##// END OF EJS Templates
allow the notebook to run without MathJax...
MinRK -
Show More
@@ -1,579 +1,581 b''
1 """Tornado handlers for the notebook.
1 """Tornado handlers for the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import logging
19 import logging
20 import Cookie
20 import Cookie
21 import uuid
21 import uuid
22
22
23 from tornado import web
23 from tornado import web
24 from tornado import websocket
24 from tornado import websocket
25
25
26 from zmq.eventloop import ioloop
26 from zmq.eventloop import ioloop
27 from zmq.utils import jsonapi
27 from zmq.utils import jsonapi
28
28
29 from IPython.external.decorator import decorator
29 from IPython.external.decorator import decorator
30 from IPython.zmq.session import Session
30 from IPython.zmq.session import Session
31 from IPython.lib.security import passwd_check
31 from IPython.lib.security import passwd_check
32
32
33 try:
33 try:
34 from docutils.core import publish_string
34 from docutils.core import publish_string
35 except ImportError:
35 except ImportError:
36 publish_string = None
36 publish_string = None
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
39 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 # Google Chrome, as of release 16, changed its websocket protocol number. The
42 # Google Chrome, as of release 16, changed its websocket protocol number. The
43 # parts tornado cares about haven't really changed, so it's OK to continue
43 # parts tornado cares about haven't really changed, so it's OK to continue
44 # accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
44 # accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
45 # version as of Oct 30/2011) the version check fails, see the issue report:
45 # version as of Oct 30/2011) the version check fails, see the issue report:
46
46
47 # https://github.com/facebook/tornado/issues/385
47 # https://github.com/facebook/tornado/issues/385
48
48
49 # This issue has been fixed in Tornado post 2.1.1:
49 # This issue has been fixed in Tornado post 2.1.1:
50
50
51 # https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
51 # https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
52
52
53 # Here we manually apply the same patch as above so that users of IPython can
53 # Here we manually apply the same patch as above so that users of IPython can
54 # continue to work with an officially released Tornado. We make the
54 # continue to work with an officially released Tornado. We make the
55 # monkeypatch version check as narrow as possible to limit its effects; once
55 # monkeypatch version check as narrow as possible to limit its effects; once
56 # Tornado 2.1.1 is no longer found in the wild we'll delete this code.
56 # Tornado 2.1.1 is no longer found in the wild we'll delete this code.
57
57
58 import tornado
58 import tornado
59
59
60 if tornado.version_info <= (2,1,1):
60 if tornado.version_info <= (2,1,1):
61
61
62 def _execute(self, transforms, *args, **kwargs):
62 def _execute(self, transforms, *args, **kwargs):
63 from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
63 from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
64
64
65 self.open_args = args
65 self.open_args = args
66 self.open_kwargs = kwargs
66 self.open_kwargs = kwargs
67
67
68 # The difference between version 8 and 13 is that in 8 the
68 # The difference between version 8 and 13 is that in 8 the
69 # client sends a "Sec-Websocket-Origin" header and in 13 it's
69 # client sends a "Sec-Websocket-Origin" header and in 13 it's
70 # simply "Origin".
70 # simply "Origin".
71 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
71 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
72 self.ws_connection = WebSocketProtocol8(self)
72 self.ws_connection = WebSocketProtocol8(self)
73 self.ws_connection.accept_connection()
73 self.ws_connection.accept_connection()
74
74
75 elif self.request.headers.get("Sec-WebSocket-Version"):
75 elif self.request.headers.get("Sec-WebSocket-Version"):
76 self.stream.write(tornado.escape.utf8(
76 self.stream.write(tornado.escape.utf8(
77 "HTTP/1.1 426 Upgrade Required\r\n"
77 "HTTP/1.1 426 Upgrade Required\r\n"
78 "Sec-WebSocket-Version: 8\r\n\r\n"))
78 "Sec-WebSocket-Version: 8\r\n\r\n"))
79 self.stream.close()
79 self.stream.close()
80
80
81 else:
81 else:
82 self.ws_connection = WebSocketProtocol76(self)
82 self.ws_connection = WebSocketProtocol76(self)
83 self.ws_connection.accept_connection()
83 self.ws_connection.accept_connection()
84
84
85 websocket.WebSocketHandler._execute = _execute
85 websocket.WebSocketHandler._execute = _execute
86 del _execute
86 del _execute
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Decorator for disabling read-only handlers
89 # Decorator for disabling read-only handlers
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 @decorator
92 @decorator
93 def not_if_readonly(f, self, *args, **kwargs):
93 def not_if_readonly(f, self, *args, **kwargs):
94 if self.application.read_only:
94 if self.application.read_only:
95 raise web.HTTPError(403, "Notebook server is read-only")
95 raise web.HTTPError(403, "Notebook server is read-only")
96 else:
96 else:
97 return f(self, *args, **kwargs)
97 return f(self, *args, **kwargs)
98
98
99 @decorator
99 @decorator
100 def authenticate_unless_readonly(f, self, *args, **kwargs):
100 def authenticate_unless_readonly(f, self, *args, **kwargs):
101 """authenticate this page *unless* readonly view is active.
101 """authenticate this page *unless* readonly view is active.
102
102
103 In read-only mode, the notebook list and print view should
103 In read-only mode, the notebook list and print view should
104 be accessible without authentication.
104 be accessible without authentication.
105 """
105 """
106
106
107 @web.authenticated
107 @web.authenticated
108 def auth_f(self, *args, **kwargs):
108 def auth_f(self, *args, **kwargs):
109 return f(self, *args, **kwargs)
109 return f(self, *args, **kwargs)
110 if self.application.read_only:
110 if self.application.read_only:
111 return f(self, *args, **kwargs)
111 return f(self, *args, **kwargs)
112 else:
112 else:
113 return auth_f(self, *args, **kwargs)
113 return auth_f(self, *args, **kwargs)
114
114
115 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
116 # Top-level handlers
116 # Top-level handlers
117 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
118
118
119 class RequestHandler(web.RequestHandler):
119 class RequestHandler(web.RequestHandler):
120 """RequestHandler with default variable setting."""
120 """RequestHandler with default variable setting."""
121
121
122 def render(*args, **kwargs):
122 def render(*args, **kwargs):
123 kwargs.setdefault('message', '')
123 kwargs.setdefault('message', '')
124 return web.RequestHandler.render(*args, **kwargs)
124 return web.RequestHandler.render(*args, **kwargs)
125
125
126 class AuthenticatedHandler(RequestHandler):
126 class AuthenticatedHandler(RequestHandler):
127 """A RequestHandler with an authenticated user."""
127 """A RequestHandler with an authenticated user."""
128
128
129 def get_current_user(self):
129 def get_current_user(self):
130 user_id = self.get_secure_cookie("username")
130 user_id = self.get_secure_cookie("username")
131 # For now the user_id should not return empty, but it could eventually
131 # For now the user_id should not return empty, but it could eventually
132 if user_id == '':
132 if user_id == '':
133 user_id = 'anonymous'
133 user_id = 'anonymous'
134 if user_id is None:
134 if user_id is None:
135 # prevent extra Invalid cookie sig warnings:
135 # prevent extra Invalid cookie sig warnings:
136 self.clear_cookie('username')
136 self.clear_cookie('username')
137 if not self.application.password and not self.application.read_only:
137 if not self.application.password and not self.application.read_only:
138 user_id = 'anonymous'
138 user_id = 'anonymous'
139 return user_id
139 return user_id
140
140
141 @property
141 @property
142 def read_only(self):
142 def read_only(self):
143 if self.application.read_only:
143 if self.application.read_only:
144 if self.application.password:
144 if self.application.password:
145 return self.get_current_user() is None
145 return self.get_current_user() is None
146 else:
146 else:
147 return True
147 return True
148 else:
148 else:
149 return False
149 return False
150
150
151 @property
151 @property
152 def ws_url(self):
152 def ws_url(self):
153 """websocket url matching the current request
153 """websocket url matching the current request
154
154
155 turns http[s]://host[:port] into
155 turns http[s]://host[:port] into
156 ws[s]://host[:port]
156 ws[s]://host[:port]
157 """
157 """
158 proto = self.request.protocol.replace('http', 'ws')
158 proto = self.request.protocol.replace('http', 'ws')
159 return "%s://%s" % (proto, self.request.host)
159 return "%s://%s" % (proto, self.request.host)
160
160
161
161
162 class ProjectDashboardHandler(AuthenticatedHandler):
162 class ProjectDashboardHandler(AuthenticatedHandler):
163
163
164 @authenticate_unless_readonly
164 @authenticate_unless_readonly
165 def get(self):
165 def get(self):
166 nbm = self.application.notebook_manager
166 nbm = self.application.notebook_manager
167 project = nbm.notebook_dir
167 project = nbm.notebook_dir
168 self.render(
168 self.render(
169 'projectdashboard.html', project=project,
169 'projectdashboard.html', project=project,
170 base_project_url=u'/', base_kernel_url=u'/',
170 base_project_url=u'/', base_kernel_url=u'/',
171 read_only=self.read_only,
171 read_only=self.read_only,
172 )
172 )
173
173
174
174
175 class LoginHandler(AuthenticatedHandler):
175 class LoginHandler(AuthenticatedHandler):
176
176
177 def _render(self, message=None):
177 def _render(self, message=None):
178 self.render('login.html',
178 self.render('login.html',
179 next=self.get_argument('next', default='/'),
179 next=self.get_argument('next', default='/'),
180 read_only=self.read_only,
180 read_only=self.read_only,
181 message=message
181 message=message
182 )
182 )
183
183
184 def get(self):
184 def get(self):
185 if self.current_user:
185 if self.current_user:
186 self.redirect(self.get_argument('next', default='/'))
186 self.redirect(self.get_argument('next', default='/'))
187 else:
187 else:
188 self._render()
188 self._render()
189
189
190 def post(self):
190 def post(self):
191 pwd = self.get_argument('password', default=u'')
191 pwd = self.get_argument('password', default=u'')
192 if self.application.password:
192 if self.application.password:
193 if passwd_check(self.application.password, pwd):
193 if passwd_check(self.application.password, pwd):
194 self.set_secure_cookie('username', str(uuid.uuid4()))
194 self.set_secure_cookie('username', str(uuid.uuid4()))
195 else:
195 else:
196 self._render(message={'error': 'Invalid password'})
196 self._render(message={'error': 'Invalid password'})
197 return
197 return
198
198
199 self.redirect(self.get_argument('next', default='/'))
199 self.redirect(self.get_argument('next', default='/'))
200
200
201
201
202 class LogoutHandler(AuthenticatedHandler):
202 class LogoutHandler(AuthenticatedHandler):
203
203
204 def get(self):
204 def get(self):
205 self.clear_cookie('username')
205 self.clear_cookie('username')
206 self.render('logout.html', message={'info': 'Successfully logged out.'})
206 self.render('logout.html', message={'info': 'Successfully logged out.'})
207
207
208
208
209 class NewHandler(AuthenticatedHandler):
209 class NewHandler(AuthenticatedHandler):
210
210
211 @web.authenticated
211 @web.authenticated
212 def get(self):
212 def get(self):
213 nbm = self.application.notebook_manager
213 nbm = self.application.notebook_manager
214 project = nbm.notebook_dir
214 project = nbm.notebook_dir
215 notebook_id = nbm.new_notebook()
215 notebook_id = nbm.new_notebook()
216 self.render(
216 self.render(
217 'notebook.html', project=project,
217 'notebook.html', project=project,
218 notebook_id=notebook_id,
218 notebook_id=notebook_id,
219 base_project_url=u'/', base_kernel_url=u'/',
219 base_project_url=u'/', base_kernel_url=u'/',
220 kill_kernel=False,
220 kill_kernel=False,
221 read_only=False,
221 read_only=False,
222 enable_mathjax=self.application.ipython_app.enable_mathjax,
222 )
223 )
223
224
224
225
225 class NamedNotebookHandler(AuthenticatedHandler):
226 class NamedNotebookHandler(AuthenticatedHandler):
226
227
227 @authenticate_unless_readonly
228 @authenticate_unless_readonly
228 def get(self, notebook_id):
229 def get(self, notebook_id):
229 nbm = self.application.notebook_manager
230 nbm = self.application.notebook_manager
230 project = nbm.notebook_dir
231 project = nbm.notebook_dir
231 if not nbm.notebook_exists(notebook_id):
232 if not nbm.notebook_exists(notebook_id):
232 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
233 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
233
234
234 self.render(
235 self.render(
235 'notebook.html', project=project,
236 'notebook.html', project=project,
236 notebook_id=notebook_id,
237 notebook_id=notebook_id,
237 base_project_url=u'/', base_kernel_url=u'/',
238 base_project_url=u'/', base_kernel_url=u'/',
238 kill_kernel=False,
239 kill_kernel=False,
239 read_only=self.read_only,
240 read_only=self.read_only,
241 enable_mathjax=self.application.ipython_app.enable_mathjax,
240 )
242 )
241
243
242
244
243 #-----------------------------------------------------------------------------
245 #-----------------------------------------------------------------------------
244 # Kernel handlers
246 # Kernel handlers
245 #-----------------------------------------------------------------------------
247 #-----------------------------------------------------------------------------
246
248
247
249
248 class MainKernelHandler(AuthenticatedHandler):
250 class MainKernelHandler(AuthenticatedHandler):
249
251
250 @web.authenticated
252 @web.authenticated
251 def get(self):
253 def get(self):
252 km = self.application.kernel_manager
254 km = self.application.kernel_manager
253 self.finish(jsonapi.dumps(km.kernel_ids))
255 self.finish(jsonapi.dumps(km.kernel_ids))
254
256
255 @web.authenticated
257 @web.authenticated
256 def post(self):
258 def post(self):
257 km = self.application.kernel_manager
259 km = self.application.kernel_manager
258 notebook_id = self.get_argument('notebook', default=None)
260 notebook_id = self.get_argument('notebook', default=None)
259 kernel_id = km.start_kernel(notebook_id)
261 kernel_id = km.start_kernel(notebook_id)
260 data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
262 data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
261 self.set_header('Location', '/'+kernel_id)
263 self.set_header('Location', '/'+kernel_id)
262 self.finish(jsonapi.dumps(data))
264 self.finish(jsonapi.dumps(data))
263
265
264
266
265 class KernelHandler(AuthenticatedHandler):
267 class KernelHandler(AuthenticatedHandler):
266
268
267 SUPPORTED_METHODS = ('DELETE')
269 SUPPORTED_METHODS = ('DELETE')
268
270
269 @web.authenticated
271 @web.authenticated
270 def delete(self, kernel_id):
272 def delete(self, kernel_id):
271 km = self.application.kernel_manager
273 km = self.application.kernel_manager
272 km.kill_kernel(kernel_id)
274 km.kill_kernel(kernel_id)
273 self.set_status(204)
275 self.set_status(204)
274 self.finish()
276 self.finish()
275
277
276
278
277 class KernelActionHandler(AuthenticatedHandler):
279 class KernelActionHandler(AuthenticatedHandler):
278
280
279 @web.authenticated
281 @web.authenticated
280 def post(self, kernel_id, action):
282 def post(self, kernel_id, action):
281 km = self.application.kernel_manager
283 km = self.application.kernel_manager
282 if action == 'interrupt':
284 if action == 'interrupt':
283 km.interrupt_kernel(kernel_id)
285 km.interrupt_kernel(kernel_id)
284 self.set_status(204)
286 self.set_status(204)
285 if action == 'restart':
287 if action == 'restart':
286 new_kernel_id = km.restart_kernel(kernel_id)
288 new_kernel_id = km.restart_kernel(kernel_id)
287 data = {'ws_url':self.ws_url,'kernel_id':new_kernel_id}
289 data = {'ws_url':self.ws_url,'kernel_id':new_kernel_id}
288 self.set_header('Location', '/'+new_kernel_id)
290 self.set_header('Location', '/'+new_kernel_id)
289 self.write(jsonapi.dumps(data))
291 self.write(jsonapi.dumps(data))
290 self.finish()
292 self.finish()
291
293
292
294
293 class ZMQStreamHandler(websocket.WebSocketHandler):
295 class ZMQStreamHandler(websocket.WebSocketHandler):
294
296
295 def _reserialize_reply(self, msg_list):
297 def _reserialize_reply(self, msg_list):
296 """Reserialize a reply message using JSON.
298 """Reserialize a reply message using JSON.
297
299
298 This takes the msg list from the ZMQ socket, unserializes it using
300 This takes the msg list from the ZMQ socket, unserializes it using
299 self.session and then serializes the result using JSON. This method
301 self.session and then serializes the result using JSON. This method
300 should be used by self._on_zmq_reply to build messages that can
302 should be used by self._on_zmq_reply to build messages that can
301 be sent back to the browser.
303 be sent back to the browser.
302 """
304 """
303 idents, msg_list = self.session.feed_identities(msg_list)
305 idents, msg_list = self.session.feed_identities(msg_list)
304 msg = self.session.unserialize(msg_list)
306 msg = self.session.unserialize(msg_list)
305 try:
307 try:
306 msg['header'].pop('date')
308 msg['header'].pop('date')
307 except KeyError:
309 except KeyError:
308 pass
310 pass
309 try:
311 try:
310 msg['parent_header'].pop('date')
312 msg['parent_header'].pop('date')
311 except KeyError:
313 except KeyError:
312 pass
314 pass
313 msg.pop('buffers')
315 msg.pop('buffers')
314 return jsonapi.dumps(msg)
316 return jsonapi.dumps(msg)
315
317
316 def _on_zmq_reply(self, msg_list):
318 def _on_zmq_reply(self, msg_list):
317 try:
319 try:
318 msg = self._reserialize_reply(msg_list)
320 msg = self._reserialize_reply(msg_list)
319 except:
321 except:
320 self.application.log.critical("Malformed message: %r" % msg_list)
322 self.application.log.critical("Malformed message: %r" % msg_list)
321 else:
323 else:
322 self.write_message(msg)
324 self.write_message(msg)
323
325
324
326
325 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
327 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
326
328
327 def open(self, kernel_id):
329 def open(self, kernel_id):
328 self.kernel_id = kernel_id.decode('ascii')
330 self.kernel_id = kernel_id.decode('ascii')
329 try:
331 try:
330 cfg = self.application.ipython_app.config
332 cfg = self.application.ipython_app.config
331 except AttributeError:
333 except AttributeError:
332 # protect from the case where this is run from something other than
334 # protect from the case where this is run from something other than
333 # the notebook app:
335 # the notebook app:
334 cfg = None
336 cfg = None
335 self.session = Session(config=cfg)
337 self.session = Session(config=cfg)
336 self.save_on_message = self.on_message
338 self.save_on_message = self.on_message
337 self.on_message = self.on_first_message
339 self.on_message = self.on_first_message
338
340
339 def get_current_user(self):
341 def get_current_user(self):
340 user_id = self.get_secure_cookie("username")
342 user_id = self.get_secure_cookie("username")
341 if user_id == '' or (user_id is None and not self.application.password):
343 if user_id == '' or (user_id is None and not self.application.password):
342 user_id = 'anonymous'
344 user_id = 'anonymous'
343 return user_id
345 return user_id
344
346
345 def _inject_cookie_message(self, msg):
347 def _inject_cookie_message(self, msg):
346 """Inject the first message, which is the document cookie,
348 """Inject the first message, which is the document cookie,
347 for authentication."""
349 for authentication."""
348 if isinstance(msg, unicode):
350 if isinstance(msg, unicode):
349 # Cookie can't constructor doesn't accept unicode strings for some reason
351 # Cookie can't constructor doesn't accept unicode strings for some reason
350 msg = msg.encode('utf8', 'replace')
352 msg = msg.encode('utf8', 'replace')
351 try:
353 try:
352 self.request._cookies = Cookie.SimpleCookie(msg)
354 self.request._cookies = Cookie.SimpleCookie(msg)
353 except:
355 except:
354 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
356 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
355
357
356 def on_first_message(self, msg):
358 def on_first_message(self, msg):
357 self._inject_cookie_message(msg)
359 self._inject_cookie_message(msg)
358 if self.get_current_user() is None:
360 if self.get_current_user() is None:
359 logging.warn("Couldn't authenticate WebSocket connection")
361 logging.warn("Couldn't authenticate WebSocket connection")
360 raise web.HTTPError(403)
362 raise web.HTTPError(403)
361 self.on_message = self.save_on_message
363 self.on_message = self.save_on_message
362
364
363
365
364 class IOPubHandler(AuthenticatedZMQStreamHandler):
366 class IOPubHandler(AuthenticatedZMQStreamHandler):
365
367
366 def initialize(self, *args, **kwargs):
368 def initialize(self, *args, **kwargs):
367 self._kernel_alive = True
369 self._kernel_alive = True
368 self._beating = False
370 self._beating = False
369 self.iopub_stream = None
371 self.iopub_stream = None
370 self.hb_stream = None
372 self.hb_stream = None
371
373
372 def on_first_message(self, msg):
374 def on_first_message(self, msg):
373 try:
375 try:
374 super(IOPubHandler, self).on_first_message(msg)
376 super(IOPubHandler, self).on_first_message(msg)
375 except web.HTTPError:
377 except web.HTTPError:
376 self.close()
378 self.close()
377 return
379 return
378 km = self.application.kernel_manager
380 km = self.application.kernel_manager
379 self.time_to_dead = km.time_to_dead
381 self.time_to_dead = km.time_to_dead
380 kernel_id = self.kernel_id
382 kernel_id = self.kernel_id
381 try:
383 try:
382 self.iopub_stream = km.create_iopub_stream(kernel_id)
384 self.iopub_stream = km.create_iopub_stream(kernel_id)
383 self.hb_stream = km.create_hb_stream(kernel_id)
385 self.hb_stream = km.create_hb_stream(kernel_id)
384 except web.HTTPError:
386 except web.HTTPError:
385 # WebSockets don't response to traditional error codes so we
387 # WebSockets don't response to traditional error codes so we
386 # close the connection.
388 # close the connection.
387 if not self.stream.closed():
389 if not self.stream.closed():
388 self.stream.close()
390 self.stream.close()
389 self.close()
391 self.close()
390 else:
392 else:
391 self.iopub_stream.on_recv(self._on_zmq_reply)
393 self.iopub_stream.on_recv(self._on_zmq_reply)
392 self.start_hb(self.kernel_died)
394 self.start_hb(self.kernel_died)
393
395
394 def on_message(self, msg):
396 def on_message(self, msg):
395 pass
397 pass
396
398
397 def on_close(self):
399 def on_close(self):
398 # This method can be called twice, once by self.kernel_died and once
400 # This method can be called twice, once by self.kernel_died and once
399 # from the WebSocket close event. If the WebSocket connection is
401 # from the WebSocket close event. If the WebSocket connection is
400 # closed before the ZMQ streams are setup, they could be None.
402 # closed before the ZMQ streams are setup, they could be None.
401 self.stop_hb()
403 self.stop_hb()
402 if self.iopub_stream is not None and not self.iopub_stream.closed():
404 if self.iopub_stream is not None and not self.iopub_stream.closed():
403 self.iopub_stream.on_recv(None)
405 self.iopub_stream.on_recv(None)
404 self.iopub_stream.close()
406 self.iopub_stream.close()
405 if self.hb_stream is not None and not self.hb_stream.closed():
407 if self.hb_stream is not None and not self.hb_stream.closed():
406 self.hb_stream.close()
408 self.hb_stream.close()
407
409
408 def start_hb(self, callback):
410 def start_hb(self, callback):
409 """Start the heartbeating and call the callback if the kernel dies."""
411 """Start the heartbeating and call the callback if the kernel dies."""
410 if not self._beating:
412 if not self._beating:
411 self._kernel_alive = True
413 self._kernel_alive = True
412
414
413 def ping_or_dead():
415 def ping_or_dead():
414 if self._kernel_alive:
416 if self._kernel_alive:
415 self._kernel_alive = False
417 self._kernel_alive = False
416 self.hb_stream.send(b'ping')
418 self.hb_stream.send(b'ping')
417 else:
419 else:
418 try:
420 try:
419 callback()
421 callback()
420 except:
422 except:
421 pass
423 pass
422 finally:
424 finally:
423 self._hb_periodic_callback.stop()
425 self._hb_periodic_callback.stop()
424
426
425 def beat_received(msg):
427 def beat_received(msg):
426 self._kernel_alive = True
428 self._kernel_alive = True
427
429
428 self.hb_stream.on_recv(beat_received)
430 self.hb_stream.on_recv(beat_received)
429 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
431 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
430 self._hb_periodic_callback.start()
432 self._hb_periodic_callback.start()
431 self._beating= True
433 self._beating= True
432
434
433 def stop_hb(self):
435 def stop_hb(self):
434 """Stop the heartbeating and cancel all related callbacks."""
436 """Stop the heartbeating and cancel all related callbacks."""
435 if self._beating:
437 if self._beating:
436 self._hb_periodic_callback.stop()
438 self._hb_periodic_callback.stop()
437 if not self.hb_stream.closed():
439 if not self.hb_stream.closed():
438 self.hb_stream.on_recv(None)
440 self.hb_stream.on_recv(None)
439
441
440 def kernel_died(self):
442 def kernel_died(self):
441 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
443 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
442 self.write_message(
444 self.write_message(
443 {'header': {'msg_type': 'status'},
445 {'header': {'msg_type': 'status'},
444 'parent_header': {},
446 'parent_header': {},
445 'content': {'execution_state':'dead'}
447 'content': {'execution_state':'dead'}
446 }
448 }
447 )
449 )
448 self.on_close()
450 self.on_close()
449
451
450
452
451 class ShellHandler(AuthenticatedZMQStreamHandler):
453 class ShellHandler(AuthenticatedZMQStreamHandler):
452
454
453 def initialize(self, *args, **kwargs):
455 def initialize(self, *args, **kwargs):
454 self.shell_stream = None
456 self.shell_stream = None
455
457
456 def on_first_message(self, msg):
458 def on_first_message(self, msg):
457 try:
459 try:
458 super(ShellHandler, self).on_first_message(msg)
460 super(ShellHandler, self).on_first_message(msg)
459 except web.HTTPError:
461 except web.HTTPError:
460 self.close()
462 self.close()
461 return
463 return
462 km = self.application.kernel_manager
464 km = self.application.kernel_manager
463 self.max_msg_size = km.max_msg_size
465 self.max_msg_size = km.max_msg_size
464 kernel_id = self.kernel_id
466 kernel_id = self.kernel_id
465 try:
467 try:
466 self.shell_stream = km.create_shell_stream(kernel_id)
468 self.shell_stream = km.create_shell_stream(kernel_id)
467 except web.HTTPError:
469 except web.HTTPError:
468 # WebSockets don't response to traditional error codes so we
470 # WebSockets don't response to traditional error codes so we
469 # close the connection.
471 # close the connection.
470 if not self.stream.closed():
472 if not self.stream.closed():
471 self.stream.close()
473 self.stream.close()
472 self.close()
474 self.close()
473 else:
475 else:
474 self.shell_stream.on_recv(self._on_zmq_reply)
476 self.shell_stream.on_recv(self._on_zmq_reply)
475
477
476 def on_message(self, msg):
478 def on_message(self, msg):
477 if len(msg) < self.max_msg_size:
479 if len(msg) < self.max_msg_size:
478 msg = jsonapi.loads(msg)
480 msg = jsonapi.loads(msg)
479 self.session.send(self.shell_stream, msg)
481 self.session.send(self.shell_stream, msg)
480
482
481 def on_close(self):
483 def on_close(self):
482 # Make sure the stream exists and is not already closed.
484 # Make sure the stream exists and is not already closed.
483 if self.shell_stream is not None and not self.shell_stream.closed():
485 if self.shell_stream is not None and not self.shell_stream.closed():
484 self.shell_stream.close()
486 self.shell_stream.close()
485
487
486
488
487 #-----------------------------------------------------------------------------
489 #-----------------------------------------------------------------------------
488 # Notebook web service handlers
490 # Notebook web service handlers
489 #-----------------------------------------------------------------------------
491 #-----------------------------------------------------------------------------
490
492
491 class NotebookRootHandler(AuthenticatedHandler):
493 class NotebookRootHandler(AuthenticatedHandler):
492
494
493 @authenticate_unless_readonly
495 @authenticate_unless_readonly
494 def get(self):
496 def get(self):
495
497
496 nbm = self.application.notebook_manager
498 nbm = self.application.notebook_manager
497 files = nbm.list_notebooks()
499 files = nbm.list_notebooks()
498 self.finish(jsonapi.dumps(files))
500 self.finish(jsonapi.dumps(files))
499
501
500 @web.authenticated
502 @web.authenticated
501 def post(self):
503 def post(self):
502 nbm = self.application.notebook_manager
504 nbm = self.application.notebook_manager
503 body = self.request.body.strip()
505 body = self.request.body.strip()
504 format = self.get_argument('format', default='json')
506 format = self.get_argument('format', default='json')
505 name = self.get_argument('name', default=None)
507 name = self.get_argument('name', default=None)
506 if body:
508 if body:
507 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
509 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
508 else:
510 else:
509 notebook_id = nbm.new_notebook()
511 notebook_id = nbm.new_notebook()
510 self.set_header('Location', '/'+notebook_id)
512 self.set_header('Location', '/'+notebook_id)
511 self.finish(jsonapi.dumps(notebook_id))
513 self.finish(jsonapi.dumps(notebook_id))
512
514
513
515
514 class NotebookHandler(AuthenticatedHandler):
516 class NotebookHandler(AuthenticatedHandler):
515
517
516 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
518 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
517
519
518 @authenticate_unless_readonly
520 @authenticate_unless_readonly
519 def get(self, notebook_id):
521 def get(self, notebook_id):
520 nbm = self.application.notebook_manager
522 nbm = self.application.notebook_manager
521 format = self.get_argument('format', default='json')
523 format = self.get_argument('format', default='json')
522 last_mod, name, data = nbm.get_notebook(notebook_id, format)
524 last_mod, name, data = nbm.get_notebook(notebook_id, format)
523
525
524 if format == u'json':
526 if format == u'json':
525 self.set_header('Content-Type', 'application/json')
527 self.set_header('Content-Type', 'application/json')
526 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
528 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
527 elif format == u'py':
529 elif format == u'py':
528 self.set_header('Content-Type', 'application/x-python')
530 self.set_header('Content-Type', 'application/x-python')
529 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
531 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
530 self.set_header('Last-Modified', last_mod)
532 self.set_header('Last-Modified', last_mod)
531 self.finish(data)
533 self.finish(data)
532
534
533 @web.authenticated
535 @web.authenticated
534 def put(self, notebook_id):
536 def put(self, notebook_id):
535 nbm = self.application.notebook_manager
537 nbm = self.application.notebook_manager
536 format = self.get_argument('format', default='json')
538 format = self.get_argument('format', default='json')
537 name = self.get_argument('name', default=None)
539 name = self.get_argument('name', default=None)
538 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
540 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
539 self.set_status(204)
541 self.set_status(204)
540 self.finish()
542 self.finish()
541
543
542 @web.authenticated
544 @web.authenticated
543 def delete(self, notebook_id):
545 def delete(self, notebook_id):
544 nbm = self.application.notebook_manager
546 nbm = self.application.notebook_manager
545 nbm.delete_notebook(notebook_id)
547 nbm.delete_notebook(notebook_id)
546 self.set_status(204)
548 self.set_status(204)
547 self.finish()
549 self.finish()
548
550
549 #-----------------------------------------------------------------------------
551 #-----------------------------------------------------------------------------
550 # RST web service handlers
552 # RST web service handlers
551 #-----------------------------------------------------------------------------
553 #-----------------------------------------------------------------------------
552
554
553
555
554 class RSTHandler(AuthenticatedHandler):
556 class RSTHandler(AuthenticatedHandler):
555
557
556 @web.authenticated
558 @web.authenticated
557 def post(self):
559 def post(self):
558 if publish_string is None:
560 if publish_string is None:
559 raise web.HTTPError(503, u'docutils not available')
561 raise web.HTTPError(503, u'docutils not available')
560 body = self.request.body.strip()
562 body = self.request.body.strip()
561 source = body
563 source = body
562 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
564 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
563 defaults = {'file_insertion_enabled': 0,
565 defaults = {'file_insertion_enabled': 0,
564 'raw_enabled': 0,
566 'raw_enabled': 0,
565 '_disable_config': 1,
567 '_disable_config': 1,
566 'stylesheet_path': 0
568 'stylesheet_path': 0
567 # 'template': template_path
569 # 'template': template_path
568 }
570 }
569 try:
571 try:
570 html = publish_string(source, writer_name='html',
572 html = publish_string(source, writer_name='html',
571 settings_overrides=defaults
573 settings_overrides=defaults
572 )
574 )
573 except:
575 except:
574 raise web.HTTPError(400, u'Invalid RST')
576 raise web.HTTPError(400, u'Invalid RST')
575 print html
577 print html
576 self.set_header('Content-Type', 'text/html')
578 self.set_header('Content-Type', 'text/html')
577 self.finish(html)
579 self.finish(html)
578
580
579
581
@@ -1,338 +1,360 b''
1 """A tornado based IPython notebook server.
1 """A tornado based IPython notebook server.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import signal
23 import signal
24 import socket
24 import socket
25 import sys
25 import sys
26 import threading
26 import threading
27 import webbrowser
27 import webbrowser
28
28
29 # Third party
29 # Third party
30 import zmq
30 import zmq
31
31
32 # Install the pyzmq ioloop. This has to be done before anything else from
32 # Install the pyzmq ioloop. This has to be done before anything else from
33 # tornado is imported.
33 # tornado is imported.
34 from zmq.eventloop import ioloop
34 from zmq.eventloop import ioloop
35 # FIXME: ioloop.install is new in pyzmq-2.1.7, so remove this conditional
35 # FIXME: ioloop.install is new in pyzmq-2.1.7, so remove this conditional
36 # when pyzmq dependency is updated beyond that.
36 # when pyzmq dependency is updated beyond that.
37 if hasattr(ioloop, 'install'):
37 if hasattr(ioloop, 'install'):
38 ioloop.install()
38 ioloop.install()
39 else:
39 else:
40 import tornado.ioloop
40 import tornado.ioloop
41 tornado.ioloop.IOLoop = ioloop.IOLoop
41 tornado.ioloop.IOLoop = ioloop.IOLoop
42
42
43 from tornado import httpserver
43 from tornado import httpserver
44 from tornado import web
44 from tornado import web
45
45
46 # Our own libraries
46 # Our own libraries
47 from .kernelmanager import MappingKernelManager
47 from .kernelmanager import MappingKernelManager
48 from .handlers import (LoginHandler, LogoutHandler,
48 from .handlers import (LoginHandler, LogoutHandler,
49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
51 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
51 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
52 )
52 )
53 from .notebookmanager import NotebookManager
53 from .notebookmanager import NotebookManager
54
54
55 from IPython.config.application import catch_config_error
55 from IPython.config.application import catch_config_error
56 from IPython.core.application import BaseIPythonApplication
56 from IPython.core.application import BaseIPythonApplication
57 from IPython.core.profiledir import ProfileDir
57 from IPython.core.profiledir import ProfileDir
58 from IPython.zmq.session import Session, default_secure
58 from IPython.zmq.session import Session, default_secure
59 from IPython.zmq.zmqshell import ZMQInteractiveShell
59 from IPython.zmq.zmqshell import ZMQInteractiveShell
60 from IPython.zmq.ipkernel import (
60 from IPython.zmq.ipkernel import (
61 flags as ipkernel_flags,
61 flags as ipkernel_flags,
62 aliases as ipkernel_aliases,
62 aliases as ipkernel_aliases,
63 IPKernelApp
63 IPKernelApp
64 )
64 )
65 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
65 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
66
66
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68 # Module globals
68 # Module globals
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70
70
71 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
71 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
72 _kernel_action_regex = r"(?P<action>restart|interrupt)"
72 _kernel_action_regex = r"(?P<action>restart|interrupt)"
73 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
73 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
74
74
75 LOCALHOST = '127.0.0.1'
75 LOCALHOST = '127.0.0.1'
76
76
77 _examples = """
77 _examples = """
78 ipython notebook # start the notebook
78 ipython notebook # start the notebook
79 ipython notebook --profile=sympy # use the sympy profile
79 ipython notebook --profile=sympy # use the sympy profile
80 ipython notebook --pylab=inline # pylab in inline plotting mode
80 ipython notebook --pylab=inline # pylab in inline plotting mode
81 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
81 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
82 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
82 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
83 """
83 """
84
84
85 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
86 # The Tornado web application
86 # The Tornado web application
87 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
88
88
89 class NotebookWebApplication(web.Application):
89 class NotebookWebApplication(web.Application):
90
90
91 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
91 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
92 handlers = [
92 handlers = [
93 (r"/", ProjectDashboardHandler),
93 (r"/", ProjectDashboardHandler),
94 (r"/login", LoginHandler),
94 (r"/login", LoginHandler),
95 (r"/logout", LogoutHandler),
95 (r"/logout", LogoutHandler),
96 (r"/new", NewHandler),
96 (r"/new", NewHandler),
97 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
97 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
98 (r"/kernels", MainKernelHandler),
98 (r"/kernels", MainKernelHandler),
99 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
99 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
100 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
100 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
101 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
101 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
102 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
102 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
103 (r"/notebooks", NotebookRootHandler),
103 (r"/notebooks", NotebookRootHandler),
104 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
104 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
105 (r"/rstservice/render", RSTHandler)
105 (r"/rstservice/render", RSTHandler)
106 ]
106 ]
107 settings = dict(
107 settings = dict(
108 template_path=os.path.join(os.path.dirname(__file__), "templates"),
108 template_path=os.path.join(os.path.dirname(__file__), "templates"),
109 static_path=os.path.join(os.path.dirname(__file__), "static"),
109 static_path=os.path.join(os.path.dirname(__file__), "static"),
110 cookie_secret=os.urandom(1024),
110 cookie_secret=os.urandom(1024),
111 login_url="/login",
111 login_url="/login",
112 )
112 )
113 web.Application.__init__(self, handlers, **settings)
113 web.Application.__init__(self, handlers, **settings)
114
114
115 self.kernel_manager = kernel_manager
115 self.kernel_manager = kernel_manager
116 self.log = log
116 self.log = log
117 self.notebook_manager = notebook_manager
117 self.notebook_manager = notebook_manager
118 self.ipython_app = ipython_app
118 self.ipython_app = ipython_app
119 self.read_only = self.ipython_app.read_only
119 self.read_only = self.ipython_app.read_only
120
120
121
121
122 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
123 # Aliases and Flags
123 # Aliases and Flags
124 #-----------------------------------------------------------------------------
124 #-----------------------------------------------------------------------------
125
125
126 flags = dict(ipkernel_flags)
126 flags = dict(ipkernel_flags)
127 flags['no-browser']=(
127 flags['no-browser']=(
128 {'NotebookApp' : {'open_browser' : False}},
128 {'NotebookApp' : {'open_browser' : False}},
129 "Don't open the notebook in a browser after startup."
129 "Don't open the notebook in a browser after startup."
130 )
130 )
131 flags['no-mathjax']=(
132 {'NotebookApp' : {'enable_mathjax' : False}},
133 """Disable MathJax
134
135 MathJax is the javascript library IPython uses to render math/LaTeX. It is
136 very large, so you may want to disable it if you have a slow internet
137 connection, or for offline use of the notebook.
138
139 When disabled, equations etc. will appear as their untransformed TeX source.
140 """
141 )
131 flags['read-only'] = (
142 flags['read-only'] = (
132 {'NotebookApp' : {'read_only' : True}},
143 {'NotebookApp' : {'read_only' : True}},
133 """Allow read-only access to notebooks.
144 """Allow read-only access to notebooks.
134
145
135 When using a password to protect the notebook server, this flag
146 When using a password to protect the notebook server, this flag
136 allows unauthenticated clients to view the notebook list, and
147 allows unauthenticated clients to view the notebook list, and
137 individual notebooks, but not edit them, start kernels, or run
148 individual notebooks, but not edit them, start kernels, or run
138 code.
149 code.
139
150
140 If no password is set, the server will be entirely read-only.
151 If no password is set, the server will be entirely read-only.
141 """
152 """
142 )
153 )
143
154
144 # the flags that are specific to the frontend
155 # the flags that are specific to the frontend
145 # these must be scrubbed before being passed to the kernel,
156 # these must be scrubbed before being passed to the kernel,
146 # or it will raise an error on unrecognized flags
157 # or it will raise an error on unrecognized flags
147 notebook_flags = ['no-browser', 'read-only']
158 notebook_flags = ['no-browser', 'no-mathjax', 'read-only']
148
159
149 aliases = dict(ipkernel_aliases)
160 aliases = dict(ipkernel_aliases)
150
161
151 aliases.update({
162 aliases.update({
152 'ip': 'NotebookApp.ip',
163 'ip': 'NotebookApp.ip',
153 'port': 'NotebookApp.port',
164 'port': 'NotebookApp.port',
154 'keyfile': 'NotebookApp.keyfile',
165 'keyfile': 'NotebookApp.keyfile',
155 'certfile': 'NotebookApp.certfile',
166 'certfile': 'NotebookApp.certfile',
156 'notebook-dir': 'NotebookManager.notebook_dir',
167 'notebook-dir': 'NotebookManager.notebook_dir',
157 })
168 })
158
169
159 # remove ipkernel flags that are singletons, and don't make sense in
170 # remove ipkernel flags that are singletons, and don't make sense in
160 # multi-kernel evironment:
171 # multi-kernel evironment:
161 aliases.pop('f', None)
172 aliases.pop('f', None)
162
173
163 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
174 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
164 u'notebook-dir']
175 u'notebook-dir']
165
176
166 #-----------------------------------------------------------------------------
177 #-----------------------------------------------------------------------------
167 # NotebookApp
178 # NotebookApp
168 #-----------------------------------------------------------------------------
179 #-----------------------------------------------------------------------------
169
180
170 class NotebookApp(BaseIPythonApplication):
181 class NotebookApp(BaseIPythonApplication):
171
182
172 name = 'ipython-notebook'
183 name = 'ipython-notebook'
173 default_config_file_name='ipython_notebook_config.py'
184 default_config_file_name='ipython_notebook_config.py'
174
185
175 description = """
186 description = """
176 The IPython HTML Notebook.
187 The IPython HTML Notebook.
177
188
178 This launches a Tornado based HTML Notebook Server that serves up an
189 This launches a Tornado based HTML Notebook Server that serves up an
179 HTML5/Javascript Notebook client.
190 HTML5/Javascript Notebook client.
180 """
191 """
181 examples = _examples
192 examples = _examples
182
193
183 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
194 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
184 MappingKernelManager, NotebookManager]
195 MappingKernelManager, NotebookManager]
185 flags = Dict(flags)
196 flags = Dict(flags)
186 aliases = Dict(aliases)
197 aliases = Dict(aliases)
187
198
188 kernel_argv = List(Unicode)
199 kernel_argv = List(Unicode)
189
200
190 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
201 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
191 default_value=logging.INFO,
202 default_value=logging.INFO,
192 config=True,
203 config=True,
193 help="Set the log level by value or name.")
204 help="Set the log level by value or name.")
194
205
195 # Network related information.
206 # Network related information.
196
207
197 ip = Unicode(LOCALHOST, config=True,
208 ip = Unicode(LOCALHOST, config=True,
198 help="The IP address the notebook server will listen on."
209 help="The IP address the notebook server will listen on."
199 )
210 )
200
211
201 def _ip_changed(self, name, old, new):
212 def _ip_changed(self, name, old, new):
202 if new == u'*': self.ip = u''
213 if new == u'*': self.ip = u''
203
214
204 port = Integer(8888, config=True,
215 port = Integer(8888, config=True,
205 help="The port the notebook server will listen on."
216 help="The port the notebook server will listen on."
206 )
217 )
207
218
208 certfile = Unicode(u'', config=True,
219 certfile = Unicode(u'', config=True,
209 help="""The full path to an SSL/TLS certificate file."""
220 help="""The full path to an SSL/TLS certificate file."""
210 )
221 )
211
222
212 keyfile = Unicode(u'', config=True,
223 keyfile = Unicode(u'', config=True,
213 help="""The full path to a private key file for usage with SSL/TLS."""
224 help="""The full path to a private key file for usage with SSL/TLS."""
214 )
225 )
215
226
216 password = Unicode(u'', config=True,
227 password = Unicode(u'', config=True,
217 help="""Hashed password to use for web authentication.
228 help="""Hashed password to use for web authentication.
218
229
219 To generate, type in a python/IPython shell:
230 To generate, type in a python/IPython shell:
220
231
221 from IPython.lib import passwd; passwd()
232 from IPython.lib import passwd; passwd()
222
233
223 The string should be of the form type:salt:hashed-password.
234 The string should be of the form type:salt:hashed-password.
224 """
235 """
225 )
236 )
226
237
227 open_browser = Bool(True, config=True,
238 open_browser = Bool(True, config=True,
228 help="Whether to open in a browser after starting.")
239 help="Whether to open in a browser after starting.")
229
240
230 read_only = Bool(False, config=True,
241 read_only = Bool(False, config=True,
231 help="Whether to prevent editing/execution of notebooks."
242 help="Whether to prevent editing/execution of notebooks."
232 )
243 )
244
245 enable_mathjax = Bool(True, config=True,
246 help="""Whether to enable MathJax for typesetting math/TeX
247
248 MathJax is the javascript library IPython uses to render math/LaTeX. It is
249 very large, so you may want to disable it if you have a slow internet
250 connection, or for offline use of the notebook.
251
252 When disabled, equations etc. will appear as their untransformed TeX source.
253 """
254 )
233
255
234 def parse_command_line(self, argv=None):
256 def parse_command_line(self, argv=None):
235 super(NotebookApp, self).parse_command_line(argv)
257 super(NotebookApp, self).parse_command_line(argv)
236 if argv is None:
258 if argv is None:
237 argv = sys.argv[1:]
259 argv = sys.argv[1:]
238
260
239 self.kernel_argv = list(argv) # copy
261 self.kernel_argv = list(argv) # copy
240 # Kernel should inherit default config file from frontend
262 # Kernel should inherit default config file from frontend
241 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
263 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
242 # Scrub frontend-specific flags
264 # Scrub frontend-specific flags
243 for a in argv:
265 for a in argv:
244 if a.startswith('-') and a.lstrip('-') in notebook_flags:
266 if a.startswith('-') and a.lstrip('-') in notebook_flags:
245 self.kernel_argv.remove(a)
267 self.kernel_argv.remove(a)
246 swallow_next = False
268 swallow_next = False
247 for a in argv:
269 for a in argv:
248 if swallow_next:
270 if swallow_next:
249 self.kernel_argv.remove(a)
271 self.kernel_argv.remove(a)
250 swallow_next = False
272 swallow_next = False
251 continue
273 continue
252 if a.startswith('-'):
274 if a.startswith('-'):
253 split = a.lstrip('-').split('=')
275 split = a.lstrip('-').split('=')
254 alias = split[0]
276 alias = split[0]
255 if alias in notebook_aliases:
277 if alias in notebook_aliases:
256 self.kernel_argv.remove(a)
278 self.kernel_argv.remove(a)
257 if len(split) == 1:
279 if len(split) == 1:
258 # alias passed with arg via space
280 # alias passed with arg via space
259 swallow_next = True
281 swallow_next = True
260
282
261 def init_configurables(self):
283 def init_configurables(self):
262 # Don't let Qt or ZMQ swallow KeyboardInterupts.
284 # Don't let Qt or ZMQ swallow KeyboardInterupts.
263 signal.signal(signal.SIGINT, signal.SIG_DFL)
285 signal.signal(signal.SIGINT, signal.SIG_DFL)
264
286
265 # force Session default to be secure
287 # force Session default to be secure
266 default_secure(self.config)
288 default_secure(self.config)
267 # Create a KernelManager and start a kernel.
289 # Create a KernelManager and start a kernel.
268 self.kernel_manager = MappingKernelManager(
290 self.kernel_manager = MappingKernelManager(
269 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
291 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
270 connection_dir = self.profile_dir.security_dir,
292 connection_dir = self.profile_dir.security_dir,
271 )
293 )
272 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
294 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
273 self.notebook_manager.list_notebooks()
295 self.notebook_manager.list_notebooks()
274
296
275 def init_logging(self):
297 def init_logging(self):
276 super(NotebookApp, self).init_logging()
298 super(NotebookApp, self).init_logging()
277 # This prevents double log messages because tornado use a root logger that
299 # This prevents double log messages because tornado use a root logger that
278 # self.log is a child of. The logging module dipatches log messages to a log
300 # self.log is a child of. The logging module dipatches log messages to a log
279 # and all of its ancenstors until propagate is set to False.
301 # and all of its ancenstors until propagate is set to False.
280 self.log.propagate = False
302 self.log.propagate = False
281
303
282 @catch_config_error
304 @catch_config_error
283 def initialize(self, argv=None):
305 def initialize(self, argv=None):
284 super(NotebookApp, self).initialize(argv)
306 super(NotebookApp, self).initialize(argv)
285 self.init_configurables()
307 self.init_configurables()
286 self.web_app = NotebookWebApplication(
308 self.web_app = NotebookWebApplication(
287 self, self.kernel_manager, self.notebook_manager, self.log
309 self, self.kernel_manager, self.notebook_manager, self.log
288 )
310 )
289 if self.certfile:
311 if self.certfile:
290 ssl_options = dict(certfile=self.certfile)
312 ssl_options = dict(certfile=self.certfile)
291 if self.keyfile:
313 if self.keyfile:
292 ssl_options['keyfile'] = self.keyfile
314 ssl_options['keyfile'] = self.keyfile
293 else:
315 else:
294 ssl_options = None
316 ssl_options = None
295 self.web_app.password = self.password
317 self.web_app.password = self.password
296 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
318 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
297 if ssl_options is None and not self.ip:
319 if ssl_options is None and not self.ip:
298 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
320 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
299 'but not using any encryption or authentication. This is highly '
321 'but not using any encryption or authentication. This is highly '
300 'insecure and not recommended.')
322 'insecure and not recommended.')
301
323
302 # Try random ports centered around the default.
324 # Try random ports centered around the default.
303 from random import randint
325 from random import randint
304 n = 50 # Max number of attempts, keep reasonably large.
326 n = 50 # Max number of attempts, keep reasonably large.
305 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
327 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
306 try:
328 try:
307 self.http_server.listen(port, self.ip)
329 self.http_server.listen(port, self.ip)
308 except socket.error, e:
330 except socket.error, e:
309 if e.errno != errno.EADDRINUSE:
331 if e.errno != errno.EADDRINUSE:
310 raise
332 raise
311 self.log.info('The port %i is already in use, trying another random port.' % port)
333 self.log.info('The port %i is already in use, trying another random port.' % port)
312 else:
334 else:
313 self.port = port
335 self.port = port
314 break
336 break
315
337
316 def start(self):
338 def start(self):
317 ip = self.ip if self.ip else '[all ip addresses on your system]'
339 ip = self.ip if self.ip else '[all ip addresses on your system]'
318 proto = 'https' if self.certfile else 'http'
340 proto = 'https' if self.certfile else 'http'
319 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
341 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
320 ip,
342 ip,
321 self.port))
343 self.port))
322 if self.open_browser:
344 if self.open_browser:
323 ip = self.ip or '127.0.0.1'
345 ip = self.ip or '127.0.0.1'
324 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
346 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
325 new=2)
347 new=2)
326 threading.Thread(target=b).start()
348 threading.Thread(target=b).start()
327
349
328 ioloop.IOLoop.instance().start()
350 ioloop.IOLoop.instance().start()
329
351
330 #-----------------------------------------------------------------------------
352 #-----------------------------------------------------------------------------
331 # Main entry point
353 # Main entry point
332 #-----------------------------------------------------------------------------
354 #-----------------------------------------------------------------------------
333
355
334 def launch_new_instance():
356 def launch_new_instance():
335 app = NotebookApp()
357 app = NotebookApp()
336 app.initialize()
358 app.initialize()
337 app.start()
359 app.start()
338
360
@@ -1,436 +1,448 b''
1 /**
1 /**
2 * Primary styles
2 * Primary styles
3 *
3 *
4 * Author: IPython Development Team
4 * Author: IPython Development Team
5 */
5 */
6
6
7
7
8 body {
8 body {
9 background-color: white;
9 background-color: white;
10 /* This makes sure that the body covers the entire window and needs to
10 /* This makes sure that the body covers the entire window and needs to
11 be in a different element than the display: box in wrapper below */
11 be in a different element than the display: box in wrapper below */
12 position: absolute;
12 position: absolute;
13 left: 0px;
13 left: 0px;
14 right: 0px;
14 right: 0px;
15 top: 0px;
15 top: 0px;
16 bottom: 0px;
16 bottom: 0px;
17 overflow: hidden;
17 overflow: hidden;
18 }
18 }
19
19
20 span#save_widget {
20 span#save_widget {
21 position: static;
21 position: static;
22 left: 0px;
22 left: 0px;
23 padding: 5px 0px;
23 padding: 5px 0px;
24 margin: 0px 0px 0px 0px;
24 margin: 0px 0px 0px 0px;
25 }
25 }
26
26
27 span#quick_help_area {
27 span#quick_help_area {
28 position: static;
28 position: static;
29 padding: 5px 0px;
29 padding: 5px 0px;
30 margin: 0px 0px 0px 0px;
30 margin: 0px 0px 0px 0px;
31 }
31 }
32
32
33 input#notebook_name {
33 input#notebook_name {
34 height: 1em;
34 height: 1em;
35 line-height: 1em;
35 line-height: 1em;
36 padding: 5px;
36 padding: 5px;
37 }
37 }
38
38
39 span#kernel_status {
39 span#kernel_status {
40 position: absolute;
40 position: absolute;
41 padding: 8px 5px 5px 5px;
41 padding: 8px 5px 5px 5px;
42 right: 10px;
42 right: 10px;
43 font-weight: bold;
43 font-weight: bold;
44 }
44 }
45
45
46
46
47 .status_idle {
47 .status_idle {
48 color: gray;
48 color: gray;
49 visibility: hidden;
49 visibility: hidden;
50 }
50 }
51
51
52 .status_busy {
52 .status_busy {
53 color: red;
53 color: red;
54 }
54 }
55
55
56 .status_restarting {
56 .status_restarting {
57 color: black;
57 color: black;
58 }
58 }
59
59
60 div#left_panel {
60 div#left_panel {
61 overflow-y: auto;
61 overflow-y: auto;
62 top: 0px;
62 top: 0px;
63 left: 0px;
63 left: 0px;
64 margin: 0px;
64 margin: 0px;
65 padding: 0px;
65 padding: 0px;
66 position: absolute;
66 position: absolute;
67 }
67 }
68
68
69 div.section_header {
69 div.section_header {
70 padding: 5px;
70 padding: 5px;
71 }
71 }
72
72
73 div.section_header h3 {
73 div.section_header h3 {
74 display: inline;
74 display: inline;
75 }
75 }
76
76
77 div.section_content {
77 div.section_content {
78 padding: 5px;
78 padding: 5px;
79 }
79 }
80
80
81 span.section_row_buttons button {
81 span.section_row_buttons button {
82 width: 70px;
82 width: 70px;
83 }
83 }
84
84
85 span.section_row_buttons a {
85 span.section_row_buttons a {
86 width: 70px;
86 width: 70px;
87 }
87 }
88
88
89 .section_row {
89 .section_row {
90 margin: 5px 0px;
90 margin: 5px 0px;
91 }
91 }
92
92
93 .section_row_buttons {
93 .section_row_buttons {
94 float: right;
94 float: right;
95 }
95 }
96
96
97 #kernel_persist {
97 #kernel_persist {
98 float: right;
98 float: right;
99 }
99 }
100
100
101 .help_string {
101 .help_string {
102 float: right;
102 float: right;
103 width: 170px;
103 width: 170px;
104 padding: 0px 5px;
104 padding: 0px 5px;
105 text-align: left;
105 text-align: left;
106 font-size: 85%;
106 font-size: 85%;
107 }
107 }
108
108
109 .help_string_label {
109 .help_string_label {
110 float: right;
110 float: right;
111 font-size: 85%;
111 font-size: 85%;
112 }
112 }
113
113
114 #autoindent_span {
114 #autoindent_span {
115 float: right;
115 float: right;
116 }
116 }
117
117
118 #timebeforetooltip_span {
118 #timebeforetooltip_span {
119 float: right;
119 float: right;
120 }
120 }
121
121
122 #tooltipontab_span {
122 #tooltipontab_span {
123 float: right;
123 float: right;
124 }
124 }
125
125
126 #smartcompleter_span {
126 #smartcompleter_span {
127 float: right;
127 float: right;
128 }
128 }
129
129
130 .checkbox_label {
130 .checkbox_label {
131 font-size: 85%;
131 font-size: 85%;
132 float: right;
132 float: right;
133 padding: 0.3em;
133 padding: 0.3em;
134 }
134 }
135
135
136 .section_row_header {
136 .section_row_header {
137 float: left;
137 float: left;
138 font-size: 85%;
138 font-size: 85%;
139 padding: 0.4em 0em;
139 padding: 0.4em 0em;
140 font-weight: bold;
140 font-weight: bold;
141 }
141 }
142
142
143 span.button_label {
143 span.button_label {
144 padding: 0.2em 1em;
144 padding: 0.2em 1em;
145 font-size: 77%;
145 font-size: 77%;
146 float: right;
146 float: right;
147 }
147 }
148
148
149 /* This is needed because FF was adding a 2px margin top and bottom. */
149 /* This is needed because FF was adding a 2px margin top and bottom. */
150 .section_row .ui-button {
150 .section_row .ui-button {
151 margin-top: 0px;
151 margin-top: 0px;
152 margin-bottom: 0px;
152 margin-bottom: 0px;
153 }
153 }
154
154
155 #download_format {
155 #download_format {
156 float: right;
156 float: right;
157 font-size: 85%;
157 font-size: 85%;
158 width: 62px;
158 width: 62px;
159 margin: 1px 5px;
159 margin: 1px 5px;
160 }
160 }
161
161
162 div#left_panel_splitter {
162 div#left_panel_splitter {
163 width: 8px;
163 width: 8px;
164 top: 0px;
164 top: 0px;
165 left: 202px;
165 left: 202px;
166 margin: 0px;
166 margin: 0px;
167 padding: 0px;
167 padding: 0px;
168 position: absolute;
168 position: absolute;
169 }
169 }
170
170
171 div#notebook_panel {
171 div#notebook_panel {
172 /* The L margin will be set in the Javascript code*/
172 /* The L margin will be set in the Javascript code*/
173 margin: 0px 0px 0px 0px;
173 margin: 0px 0px 0px 0px;
174 padding: 0px;
174 padding: 0px;
175 }
175 }
176
176
177 div#notebook {
177 div#notebook {
178 overflow-y: scroll;
178 overflow-y: scroll;
179 overflow-x: auto;
179 overflow-x: auto;
180 width: 100%;
180 width: 100%;
181 /* This spaces the cell away from the edge of the notebook area */
181 /* This spaces the cell away from the edge of the notebook area */
182 padding: 5px 5px 15px 5px;
182 padding: 5px 5px 15px 5px;
183 margin: 0px
183 margin: 0px
184 background-color: white;
184 background-color: white;
185 }
185 }
186
186
187 div#pager_splitter {
187 div#pager_splitter {
188 height: 8px;
188 height: 8px;
189 }
189 }
190
190
191 div#pager {
191 div#pager {
192 padding: 15px;
192 padding: 15px;
193 overflow: auto;
193 overflow: auto;
194 }
194 }
195
195
196 div.cell {
196 div.cell {
197 width: 100%;
197 width: 100%;
198 padding: 5px 5px 5px 0px;
198 padding: 5px 5px 5px 0px;
199 /* This acts as a spacer between cells, that is outside the border */
199 /* This acts as a spacer between cells, that is outside the border */
200 margin: 2px 0px 2px 0px;
200 margin: 2px 0px 2px 0px;
201 }
201 }
202
202
203 div.code_cell {
203 div.code_cell {
204 background-color: white;
204 background-color: white;
205 }
205 }
206 /* any special styling for code cells that are currently running goes here */
206 /* any special styling for code cells that are currently running goes here */
207 div.code_cell.running {
207 div.code_cell.running {
208 }
208 }
209
209
210 div.prompt {
210 div.prompt {
211 /* This needs to be wide enough for 3 digit prompt numbers: In[100]: */
211 /* This needs to be wide enough for 3 digit prompt numbers: In[100]: */
212 width: 11ex;
212 width: 11ex;
213 /* This 0.4em is tuned to match the padding on the CodeMirror editor. */
213 /* This 0.4em is tuned to match the padding on the CodeMirror editor. */
214 padding: 0.4em;
214 padding: 0.4em;
215 margin: 0px;
215 margin: 0px;
216 font-family: monospace;
216 font-family: monospace;
217 text-align:right;
217 text-align:right;
218 }
218 }
219
219
220 div.input {
220 div.input {
221 page-break-inside: avoid;
221 page-break-inside: avoid;
222 }
222 }
223
223
224 /* input_area and input_prompt must match in top border and margin for alignment */
224 /* input_area and input_prompt must match in top border and margin for alignment */
225 div.input_area {
225 div.input_area {
226 color: black;
226 color: black;
227 border: 1px solid #ddd;
227 border: 1px solid #ddd;
228 border-radius: 3px;
228 border-radius: 3px;
229 background: #f7f7f7;
229 background: #f7f7f7;
230 }
230 }
231
231
232 div.input_prompt {
232 div.input_prompt {
233 color: navy;
233 color: navy;
234 border-top: 1px solid transparent;
234 border-top: 1px solid transparent;
235 }
235 }
236
236
237 div.output {
237 div.output {
238 /* This is a spacer between the input and output of each cell */
238 /* This is a spacer between the input and output of each cell */
239 margin-top: 5px;
239 margin-top: 5px;
240 }
240 }
241
241
242 div.output_prompt {
242 div.output_prompt {
243 color: darkred;
243 color: darkred;
244 }
244 }
245
245
246 /* This class is the outer container of all output sections. */
246 /* This class is the outer container of all output sections. */
247 div.output_area {
247 div.output_area {
248 padding: 0px;
248 padding: 0px;
249 page-break-inside: avoid;
249 page-break-inside: avoid;
250 }
250 }
251
251
252 /* This class is for the output subarea inside the output_area and after
252 /* This class is for the output subarea inside the output_area and after
253 the prompt div. */
253 the prompt div. */
254 div.output_subarea {
254 div.output_subarea {
255 padding: 0.4em 6.1em 0.4em 0.4em;
255 padding: 0.4em 6.1em 0.4em 0.4em;
256 }
256 }
257
257
258 /* The rest of the output_* classes are for special styling of the different
258 /* The rest of the output_* classes are for special styling of the different
259 output types */
259 output types */
260
260
261 /* all text output has this class: */
261 /* all text output has this class: */
262 div.output_text {
262 div.output_text {
263 text-align: left;
263 text-align: left;
264 color: black;
264 color: black;
265 font-family: monospace;
265 font-family: monospace;
266 }
266 }
267
267
268 /* stdout/stderr are 'text' as well as 'stream', but pyout/pyerr are *not* streams */
268 /* stdout/stderr are 'text' as well as 'stream', but pyout/pyerr are *not* streams */
269 div.output_stream {
269 div.output_stream {
270 padding-top: 0.0em;
270 padding-top: 0.0em;
271 padding-bottom: 0.0em;
271 padding-bottom: 0.0em;
272 }
272 }
273 div.output_stdout {
273 div.output_stdout {
274 }
274 }
275 div.output_stderr {
275 div.output_stderr {
276 background: #fdd; /* very light red background for stderr */
276 background: #fdd; /* very light red background for stderr */
277 }
277 }
278
278
279 div.output_latex {
279 div.output_latex {
280 text-align: left;
280 text-align: left;
281 color: black;
281 color: black;
282 }
282 }
283
283
284 div.output_html {
284 div.output_html {
285 }
285 }
286
286
287 div.output_png {
287 div.output_png {
288 }
288 }
289
289
290 div.output_jpeg {
290 div.output_jpeg {
291 }
291 }
292
292
293 div.text_cell {
293 div.text_cell {
294 background-color: white;
294 background-color: white;
295 }
295 }
296
296
297 div.text_cell_input {
297 div.text_cell_input {
298 color: black;
298 color: black;
299 }
299 }
300
300
301 div.text_cell_render {
301 div.text_cell_render {
302 font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
302 font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
303 outline: none;
303 outline: none;
304 resize: none;
304 resize: none;
305 width: inherit;
305 width: inherit;
306 border-style: none;
306 border-style: none;
307 padding: 5px;
307 padding: 5px;
308 color: black;
308 color: black;
309 }
309 }
310
310
311 .CodeMirror {
311 .CodeMirror {
312 line-height: 1.231; /* Changed from 1em to our global default */
312 line-height: 1.231; /* Changed from 1em to our global default */
313 }
313 }
314
314
315 .CodeMirror-scroll {
315 .CodeMirror-scroll {
316 height: auto; /* Changed to auto to autogrow */
316 height: auto; /* Changed to auto to autogrow */
317 /* The CodeMirror docs are a bit fuzzy on if overflow-y should be hidden or visible.*/
317 /* The CodeMirror docs are a bit fuzzy on if overflow-y should be hidden or visible.*/
318 /* We have found that if it is visible, vertical scrollbars appear with font size changes.*/
318 /* We have found that if it is visible, vertical scrollbars appear with font size changes.*/
319 overflow-y: hidden;
319 overflow-y: hidden;
320 overflow-x: auto; /* Changed from auto to remove scrollbar */
320 overflow-x: auto; /* Changed from auto to remove scrollbar */
321 }
321 }
322
322
323 /* CSS font colors for translated ANSI colors. */
323 /* CSS font colors for translated ANSI colors. */
324
324
325
325
326 .ansiblack {color: black;}
326 .ansiblack {color: black;}
327 .ansired {color: darkred;}
327 .ansired {color: darkred;}
328 .ansigreen {color: darkgreen;}
328 .ansigreen {color: darkgreen;}
329 .ansiyellow {color: brown;}
329 .ansiyellow {color: brown;}
330 .ansiblue {color: darkblue;}
330 .ansiblue {color: darkblue;}
331 .ansipurple {color: darkviolet;}
331 .ansipurple {color: darkviolet;}
332 .ansicyan {color: steelblue;}
332 .ansicyan {color: steelblue;}
333 .ansigrey {color: grey;}
333 .ansigrey {color: grey;}
334 .ansibold {font-weight: bold;}
334 .ansibold {font-weight: bold;}
335
335
336 .completions , .tooltip{
336 .completions , .tooltip{
337 position: absolute;
337 position: absolute;
338 z-index: 10;
338 z-index: 10;
339 overflow: auto;
339 overflow: auto;
340 border: 1px solid black;
340 border: 1px solid black;
341 }
341 }
342
342
343 .completions select {
343 .completions select {
344 background: white;
344 background: white;
345 outline: none;
345 outline: none;
346 border: none;
346 border: none;
347 padding: 0px;
347 padding: 0px;
348 margin: 0px;
348 margin: 0px;
349 font-family: monospace;
349 font-family: monospace;
350 }
350 }
351
351
352 @-moz-keyframes fadeIn {
352 @-moz-keyframes fadeIn {
353 from {opacity:0;}
353 from {opacity:0;}
354 to {opacity:1;}
354 to {opacity:1;}
355 }
355 }
356
356
357 @-webkit-keyframes fadeIn {
357 @-webkit-keyframes fadeIn {
358 from {opacity:0;}
358 from {opacity:0;}
359 to {opacity:1;}
359 to {opacity:1;}
360 }
360 }
361
361
362 @keyframes fadeIn {
362 @keyframes fadeIn {
363 from {opacity:0;}
363 from {opacity:0;}
364 to {opacity:1;}
364 to {opacity:1;}
365 }
365 }
366
366
367 /*"close" "expand" and "Open in pager button" of
367 /*"close" "expand" and "Open in pager button" of
368 /* the tooltip*/
368 /* the tooltip*/
369 .tooltip a{
369 .tooltip a{
370 float:right;
370 float:right;
371 }
371 }
372
372
373 /*properties of tooltip after "expand"*/
373 /*properties of tooltip after "expand"*/
374 .bigtooltip{
374 .bigtooltip{
375 height:30%;
375 height:30%;
376 }
376 }
377
377
378 /*properties of tooltip before "expand"*/
378 /*properties of tooltip before "expand"*/
379 .smalltooltip{
379 .smalltooltip{
380 text-overflow: ellipsis;
380 text-overflow: ellipsis;
381 overflow: hidden;
381 overflow: hidden;
382 height:15%;
382 height:15%;
383 }
383 }
384
384
385 .tooltip{
385 .tooltip{
386 /*transition when "expand"ing tooltip */
386 /*transition when "expand"ing tooltip */
387 -webkit-transition-property: height;
387 -webkit-transition-property: height;
388 -webkit-transition-duration: 1s;
388 -webkit-transition-duration: 1s;
389 -moz-transition-property: height;
389 -moz-transition-property: height;
390 -moz-transition-duration: 1s;
390 -moz-transition-duration: 1s;
391 transition-property: height;
391 transition-property: height;
392 transition-duration: 1s;
392 transition-duration: 1s;
393 max-width:700px;
393 max-width:700px;
394 border-radius: 0px 10px 10px 10px;
394 border-radius: 0px 10px 10px 10px;
395 box-shadow: 3px 3px 5px #999;
395 box-shadow: 3px 3px 5px #999;
396 /*fade-in animation when inserted*/
396 /*fade-in animation when inserted*/
397 -webkit-animation: fadeIn 200ms;
397 -webkit-animation: fadeIn 200ms;
398 -moz-animation: fadeIn 200ms;
398 -moz-animation: fadeIn 200ms;
399 animation: fadeIn 200ms;
399 animation: fadeIn 200ms;
400 vertical-align: middle;
400 vertical-align: middle;
401 background: #FDFDD8;
401 background: #FDFDD8;
402 outline: none;
402 outline: none;
403 padding: 3px;
403 padding: 3px;
404 margin: 0px;
404 margin: 0px;
405 font-family: monospace;
405 font-family: monospace;
406 min-height:50px;
406 min-height:50px;
407 }
407 }
408
408
409 .completions p{
409 .completions p{
410 background: #DDF;
410 background: #DDF;
411 /*outline: none;
411 /*outline: none;
412 padding: 0px;*/
412 padding: 0px;*/
413 border-bottom: black solid 1px;
413 border-bottom: black solid 1px;
414 padding: 1px;
414 padding: 1px;
415 font-family: monospace;
415 font-family: monospace;
416 }
416 }
417
417
418 pre.dialog {
419 background-color: #f7f7f7;
420 border: 1px solid #ddd;
421 border-radius: 3px;
422 padding: 0.4em;
423 padding-left: 2em;
424 }
425
426 p.dialog{
427 padding : 0.2em;
428 }
429
418 @media print {
430 @media print {
419 body { overflow: visible !important; }
431 body { overflow: visible !important; }
420 .ui-widget-content { border: 0px; }
432 .ui-widget-content { border: 0px; }
421 }
433 }
422
434
423 .shortcut_key {
435 .shortcut_key {
424 display: inline-block;
436 display: inline-block;
425 width: 13ex;
437 width: 13ex;
426 text-align: right;
438 text-align: right;
427 font-family: monospace;
439 font-family: monospace;
428 }
440 }
429
441
430 .shortcut_descr {
442 .shortcut_descr {
431 }
443 }
432
444
433 /* Word-wrap output correctly. This is the CSS3 spelling, though Firefox seems
445 /* Word-wrap output correctly. This is the CSS3 spelling, though Firefox seems
434 to not honor it correctly. Webkit browsers (Chrome, rekonq, Safari) do.
446 to not honor it correctly. Webkit browsers (Chrome, rekonq, Safari) do.
435 */
447 */
436 pre, code, kbd, samp { white-space: pre-wrap; }
448 pre, code, kbd, samp { white-space: pre-wrap; }
@@ -1,96 +1,103 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Cell
9 // Cell
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16 var Cell = function (notebook) {
16 var Cell = function (notebook) {
17 this.notebook = notebook;
17 this.notebook = notebook;
18 this.read_only = false;
18 this.read_only = false;
19 if (notebook){
19 if (notebook){
20 this.read_only = notebook.read_only;
20 this.read_only = notebook.read_only;
21 }
21 }
22 this.selected = false;
22 this.selected = false;
23 this.element = null;
23 this.element = null;
24 this.create_element();
24 this.create_element();
25 if (this.element !== null) {
25 if (this.element !== null) {
26 this.set_autoindent(true);
26 this.set_autoindent(true);
27 this.element.data("cell", this);
27 this.element.data("cell", this);
28 this.bind_events();
28 this.bind_events();
29 }
29 }
30 this.cell_id = utils.uuid();
30 this.cell_id = utils.uuid();
31 };
31 };
32
32
33
33
34 Cell.prototype.select = function () {
34 Cell.prototype.select = function () {
35 this.element.addClass('ui-widget-content ui-corner-all');
35 this.element.addClass('ui-widget-content ui-corner-all');
36 this.selected = true;
36 this.selected = true;
37 };
37 };
38
38
39
39
40 Cell.prototype.unselect = function () {
40 Cell.prototype.unselect = function () {
41 this.element.removeClass('ui-widget-content ui-corner-all');
41 this.element.removeClass('ui-widget-content ui-corner-all');
42 this.selected = false;
42 this.selected = false;
43 };
43 };
44
44
45
45
46 Cell.prototype.bind_events = function () {
46 Cell.prototype.bind_events = function () {
47 var that = this;
47 var that = this;
48 var nb = that.notebook;
48 var nb = that.notebook;
49 that.element.click(function (event) {
49 that.element.click(function (event) {
50 if (that.selected === false) {
50 if (that.selected === false) {
51 nb.select(nb.find_cell_index(that));
51 nb.select(nb.find_cell_index(that));
52 }
52 }
53 });
53 });
54 that.element.focusin(function (event) {
54 that.element.focusin(function (event) {
55 if (that.selected === false) {
55 if (that.selected === false) {
56 nb.select(nb.find_cell_index(that));
56 nb.select(nb.find_cell_index(that));
57 }
57 }
58 });
58 });
59 };
59 };
60
60
61 Cell.prototype.grow = function(element) {
61 Cell.prototype.grow = function(element) {
62 // Grow the cell by hand. This is used upon reloading from JSON, when the
62 // Grow the cell by hand. This is used upon reloading from JSON, when the
63 // autogrow handler is not called.
63 // autogrow handler is not called.
64 var dom = element.get(0);
64 var dom = element.get(0);
65 var lines_count = 0;
65 var lines_count = 0;
66 // modified split rule from
66 // modified split rule from
67 // http://stackoverflow.com/questions/2035910/how-to-get-the-number-of-lines-in-a-textarea/2036424#2036424
67 // http://stackoverflow.com/questions/2035910/how-to-get-the-number-of-lines-in-a-textarea/2036424#2036424
68 var lines = dom.value.split(/\r|\r\n|\n/);
68 var lines = dom.value.split(/\r|\r\n|\n/);
69 lines_count = lines.length;
69 lines_count = lines.length;
70 if (lines_count >= 1) {
70 if (lines_count >= 1) {
71 dom.rows = lines_count;
71 dom.rows = lines_count;
72 } else {
72 } else {
73 dom.rows = 1;
73 dom.rows = 1;
74 }
74 }
75 };
75 };
76
76
77
77
78 Cell.prototype.set_autoindent = function (state) {
78 Cell.prototype.set_autoindent = function (state) {
79 if (state) {
79 if (state) {
80 this.code_mirror.setOption('tabMode', 'indent');
80 this.code_mirror.setOption('tabMode', 'indent');
81 this.code_mirror.setOption('enterMode', 'indent');
81 this.code_mirror.setOption('enterMode', 'indent');
82 } else {
82 } else {
83 this.code_mirror.setOption('tabMode', 'shift');
83 this.code_mirror.setOption('tabMode', 'shift');
84 this.code_mirror.setOption('enterMode', 'flat');
84 this.code_mirror.setOption('enterMode', 'flat');
85 }
85 }
86 };
86 };
87
87
88 // Subclasses must implement create_element.
88 // Subclasses must implement create_element.
89 Cell.prototype.create_element = function () {};
89 Cell.prototype.create_element = function () {};
90
90
91 // typeset with MathJax if MathJax is available
92 Cell.prototype.typeset = function () {
93 if (window.MathJax){
94 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
95 }
96 };
97
91 IPython.Cell = Cell;
98 IPython.Cell = Cell;
92
99
93 return IPython;
100 return IPython;
94
101
95 }(IPython));
102 }(IPython));
96
103
@@ -1,795 +1,795 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // CodeCell
9 // CodeCell
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16 var CodeCell = function (notebook) {
16 var CodeCell = function (notebook) {
17 this.code_mirror = null;
17 this.code_mirror = null;
18 this.input_prompt_number = '&nbsp;';
18 this.input_prompt_number = '&nbsp;';
19 this.is_completing = false;
19 this.is_completing = false;
20 this.completion_cursor = null;
20 this.completion_cursor = null;
21 this.outputs = [];
21 this.outputs = [];
22 this.collapsed = false;
22 this.collapsed = false;
23 IPython.Cell.apply(this, arguments);
23 IPython.Cell.apply(this, arguments);
24 };
24 };
25
25
26
26
27 CodeCell.prototype = new IPython.Cell();
27 CodeCell.prototype = new IPython.Cell();
28
28
29
29
30 CodeCell.prototype.create_element = function () {
30 CodeCell.prototype.create_element = function () {
31 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
31 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
32 cell.attr('tabindex','2');
32 cell.attr('tabindex','2');
33 var input = $('<div></div>').addClass('input hbox');
33 var input = $('<div></div>').addClass('input hbox');
34 input.append($('<div/>').addClass('prompt input_prompt'));
34 input.append($('<div/>').addClass('prompt input_prompt'));
35 var input_area = $('<div/>').addClass('input_area box-flex1');
35 var input_area = $('<div/>').addClass('input_area box-flex1');
36 this.code_mirror = CodeMirror(input_area.get(0), {
36 this.code_mirror = CodeMirror(input_area.get(0), {
37 indentUnit : 4,
37 indentUnit : 4,
38 mode: 'python',
38 mode: 'python',
39 theme: 'ipython',
39 theme: 'ipython',
40 readOnly: this.read_only,
40 readOnly: this.read_only,
41 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
41 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
42 });
42 });
43 input.append(input_area);
43 input.append(input_area);
44 var output = $('<div></div>').addClass('output vbox');
44 var output = $('<div></div>').addClass('output vbox');
45 cell.append(input).append(output);
45 cell.append(input).append(output);
46 this.element = cell;
46 this.element = cell;
47 this.collapse();
47 this.collapse();
48 };
48 };
49
49
50 //TODO, try to diminish the number of parameters.
50 //TODO, try to diminish the number of parameters.
51 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time,that){
51 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time,that){
52 if (pre_cursor === "" || pre_cursor === "(" ) {
52 if (pre_cursor === "" || pre_cursor === "(" ) {
53 // don't do anything if line beggin with '(' or is empty
53 // don't do anything if line beggin with '(' or is empty
54 } else {
54 } else {
55 // Will set a timer to request tooltip in `time`
55 // Will set a timer to request tooltip in `time`
56 that.tooltip_timeout = setTimeout(function(){
56 that.tooltip_timeout = setTimeout(function(){
57 IPython.notebook.request_tool_tip(that, pre_cursor)
57 IPython.notebook.request_tool_tip(that, pre_cursor)
58 },time);
58 },time);
59 }
59 }
60 };
60 };
61
61
62 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
62 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
63 // This method gets called in CodeMirror's onKeyDown/onKeyPress
63 // This method gets called in CodeMirror's onKeyDown/onKeyPress
64 // handlers and is used to provide custom key handling. Its return
64 // handlers and is used to provide custom key handling. Its return
65 // value is used to determine if CodeMirror should ignore the event:
65 // value is used to determine if CodeMirror should ignore the event:
66 // true = ignore, false = don't ignore.
66 // true = ignore, false = don't ignore.
67
67
68 // note that we are comparing and setting the time to wait at each key press.
68 // note that we are comparing and setting the time to wait at each key press.
69 // a better wqy might be to generate a new function on each time change and
69 // a better wqy might be to generate a new function on each time change and
70 // assign it to CodeCell.prototype.request_tooltip_after_time
70 // assign it to CodeCell.prototype.request_tooltip_after_time
71 tooltip_wait_time = this.notebook.time_before_tooltip;
71 tooltip_wait_time = this.notebook.time_before_tooltip;
72 tooltip_on_tab = this.notebook.tooltip_on_tab;
72 tooltip_on_tab = this.notebook.tooltip_on_tab;
73 var that = this;
73 var that = this;
74 // whatever key is pressed, first, cancel the tooltip request before
74 // whatever key is pressed, first, cancel the tooltip request before
75 // they are sent, and remove tooltip if any
75 // they are sent, and remove tooltip if any
76 if(event.type === 'keydown' ){
76 if(event.type === 'keydown' ){
77 CodeCell.prototype.remove_and_cancel_tooltip(that.tooltip_timeout);
77 CodeCell.prototype.remove_and_cancel_tooltip(that.tooltip_timeout);
78 that.tooltip_timeout=null;
78 that.tooltip_timeout=null;
79 }
79 }
80
80
81 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
81 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
82 // Always ignore shift-enter in CodeMirror as we handle it.
82 // Always ignore shift-enter in CodeMirror as we handle it.
83 return true;
83 return true;
84 }else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
84 }else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
85 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
85 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
86 // browser and keyboard layout !
86 // browser and keyboard layout !
87 // Pressing '(' , request tooltip, don't forget to reappend it
87 // Pressing '(' , request tooltip, don't forget to reappend it
88 var cursor = editor.getCursor();
88 var cursor = editor.getCursor();
89 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
89 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
90 CodeCell.prototype.request_tooltip_after_time(pre_cursor,tooltip_wait_time,that);
90 CodeCell.prototype.request_tooltip_after_time(pre_cursor,tooltip_wait_time,that);
91 } else if (event.keyCode === 9 && event.type == 'keydown') {
91 } else if (event.keyCode === 9 && event.type == 'keydown') {
92 // Tab completion.
92 // Tab completion.
93 var cur = editor.getCursor();
93 var cur = editor.getCursor();
94 //Do not trim here because of tooltip
94 //Do not trim here because of tooltip
95 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
95 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
96 if (pre_cursor.trim() === "") {
96 if (pre_cursor.trim() === "") {
97 // Don't autocomplete if the part of the line before the cursor
97 // Don't autocomplete if the part of the line before the cursor
98 // is empty. In this case, let CodeMirror handle indentation.
98 // is empty. In this case, let CodeMirror handle indentation.
99 return false;
99 return false;
100 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
100 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
101 CodeCell.prototype.request_tooltip_after_time(pre_cursor,0,that);
101 CodeCell.prototype.request_tooltip_after_time(pre_cursor,0,that);
102 } else {
102 } else {
103 pre_cursor.trim();
103 pre_cursor.trim();
104 // Autocomplete the current line.
104 // Autocomplete the current line.
105 event.stop();
105 event.stop();
106 var line = editor.getLine(cur.line);
106 var line = editor.getLine(cur.line);
107 this.is_completing = true;
107 this.is_completing = true;
108 this.completion_cursor = cur;
108 this.completion_cursor = cur;
109 IPython.notebook.complete_cell(this, line, cur.ch);
109 IPython.notebook.complete_cell(this, line, cur.ch);
110 return true;
110 return true;
111 }
111 }
112 } else if (event.keyCode === 8 && event.type == 'keydown') {
112 } else if (event.keyCode === 8 && event.type == 'keydown') {
113 // If backspace and the line ends with 4 spaces, remove them.
113 // If backspace and the line ends with 4 spaces, remove them.
114 var cur = editor.getCursor();
114 var cur = editor.getCursor();
115 var line = editor.getLine(cur.line);
115 var line = editor.getLine(cur.line);
116 var ending = line.slice(-4);
116 var ending = line.slice(-4);
117 if (ending === ' ') {
117 if (ending === ' ') {
118 editor.replaceRange('',
118 editor.replaceRange('',
119 {line: cur.line, ch: cur.ch-4},
119 {line: cur.line, ch: cur.ch-4},
120 {line: cur.line, ch: cur.ch}
120 {line: cur.line, ch: cur.ch}
121 );
121 );
122 event.stop();
122 event.stop();
123 return true;
123 return true;
124 } else {
124 } else {
125 return false;
125 return false;
126 }
126 }
127 } else if (event.keyCode === 76 && event.ctrlKey && event.shiftKey
127 } else if (event.keyCode === 76 && event.ctrlKey && event.shiftKey
128 && event.type == 'keydown') {
128 && event.type == 'keydown') {
129 // toggle line numbers with Ctrl-Shift-L
129 // toggle line numbers with Ctrl-Shift-L
130 this.toggle_line_numbers();
130 this.toggle_line_numbers();
131 }
131 }
132 else {
132 else {
133 // keypress/keyup also trigger on TAB press, and we don't want to
133 // keypress/keyup also trigger on TAB press, and we don't want to
134 // use those to disable tab completion.
134 // use those to disable tab completion.
135 if (this.is_completing && event.keyCode !== 9) {
135 if (this.is_completing && event.keyCode !== 9) {
136 var ed_cur = editor.getCursor();
136 var ed_cur = editor.getCursor();
137 var cc_cur = this.completion_cursor;
137 var cc_cur = this.completion_cursor;
138 if (ed_cur.line !== cc_cur.line || ed_cur.ch !== cc_cur.ch) {
138 if (ed_cur.line !== cc_cur.line || ed_cur.ch !== cc_cur.ch) {
139 this.is_completing = false;
139 this.is_completing = false;
140 this.completion_cursor = null;
140 this.completion_cursor = null;
141 }
141 }
142 }
142 }
143 return false;
143 return false;
144 };
144 };
145 return false;
145 return false;
146 };
146 };
147
147
148 CodeCell.prototype.remove_and_cancel_tooltip = function(timeout)
148 CodeCell.prototype.remove_and_cancel_tooltip = function(timeout)
149 {
149 {
150 // note that we don't handle closing directly inside the calltip
150 // note that we don't handle closing directly inside the calltip
151 // as in the completer, because it is not focusable, so won't
151 // as in the completer, because it is not focusable, so won't
152 // get the event.
152 // get the event.
153 if(timeout != null)
153 if(timeout != null)
154 { clearTimeout(timeout);}
154 { clearTimeout(timeout);}
155 $('#tooltip').remove();
155 $('#tooltip').remove();
156 }
156 }
157
157
158 CodeCell.prototype.finish_tooltip = function (reply) {
158 CodeCell.prototype.finish_tooltip = function (reply) {
159 defstring=reply.definition;
159 defstring=reply.definition;
160 docstring=reply.docstring;
160 docstring=reply.docstring;
161 if(docstring == null){docstring="<empty docstring>"};
161 if(docstring == null){docstring="<empty docstring>"};
162 name=reply.name;
162 name=reply.name;
163
163
164 var that = this;
164 var that = this;
165 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
165 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
166 // remove to have the tooltip not Limited in X and Y
166 // remove to have the tooltip not Limited in X and Y
167 tooltip.addClass('smalltooltip');
167 tooltip.addClass('smalltooltip');
168 var pre=$('<pre/>').html(utils.fixConsole(docstring));
168 var pre=$('<pre/>').html(utils.fixConsole(docstring));
169 var expandlink=$('<a/>').attr('href',"#");
169 var expandlink=$('<a/>').attr('href',"#");
170 expandlink.addClass("ui-corner-all"); //rounded corner
170 expandlink.addClass("ui-corner-all"); //rounded corner
171 expandlink.attr('role',"button");
171 expandlink.attr('role',"button");
172 //expandlink.addClass('ui-button');
172 //expandlink.addClass('ui-button');
173 //expandlink.addClass('ui-state-default');
173 //expandlink.addClass('ui-state-default');
174 var expandspan=$('<span/>').text('Expand');
174 var expandspan=$('<span/>').text('Expand');
175 expandspan.addClass('ui-icon');
175 expandspan.addClass('ui-icon');
176 expandspan.addClass('ui-icon-plus');
176 expandspan.addClass('ui-icon-plus');
177 expandlink.append(expandspan);
177 expandlink.append(expandspan);
178 expandlink.attr('id','expanbutton');
178 expandlink.attr('id','expanbutton');
179 expandlink.click(function(){
179 expandlink.click(function(){
180 tooltip.removeClass('smalltooltip');
180 tooltip.removeClass('smalltooltip');
181 tooltip.addClass('bigtooltip');
181 tooltip.addClass('bigtooltip');
182 $('#expanbutton').remove();
182 $('#expanbutton').remove();
183 setTimeout(function(){that.code_mirror.focus();}, 50);
183 setTimeout(function(){that.code_mirror.focus();}, 50);
184 });
184 });
185 var morelink=$('<a/>').attr('href',"#");
185 var morelink=$('<a/>').attr('href',"#");
186 morelink.attr('role',"button");
186 morelink.attr('role',"button");
187 morelink.addClass('ui-button');
187 morelink.addClass('ui-button');
188 //morelink.addClass("ui-corner-all"); //rounded corner
188 //morelink.addClass("ui-corner-all"); //rounded corner
189 //morelink.addClass('ui-state-default');
189 //morelink.addClass('ui-state-default');
190 var morespan=$('<span/>').text('Open in Pager');
190 var morespan=$('<span/>').text('Open in Pager');
191 morespan.addClass('ui-icon');
191 morespan.addClass('ui-icon');
192 morespan.addClass('ui-icon-arrowstop-l-n');
192 morespan.addClass('ui-icon-arrowstop-l-n');
193 morelink.append(morespan);
193 morelink.append(morespan);
194 morelink.click(function(){
194 morelink.click(function(){
195 var msg_id = IPython.notebook.kernel.execute(name+"?");
195 var msg_id = IPython.notebook.kernel.execute(name+"?");
196 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.selected_cell().cell_id;
196 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.selected_cell().cell_id;
197 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
197 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
198 setTimeout(function(){that.code_mirror.focus();}, 50);
198 setTimeout(function(){that.code_mirror.focus();}, 50);
199 });
199 });
200
200
201 var closelink=$('<a/>').attr('href',"#");
201 var closelink=$('<a/>').attr('href',"#");
202 closelink.attr('role',"button");
202 closelink.attr('role',"button");
203 closelink.addClass('ui-button');
203 closelink.addClass('ui-button');
204 //closelink.addClass("ui-corner-all"); //rounded corner
204 //closelink.addClass("ui-corner-all"); //rounded corner
205 //closelink.adClass('ui-state-default'); // grey background and blue cross
205 //closelink.adClass('ui-state-default'); // grey background and blue cross
206 var closespan=$('<span/>').text('Close');
206 var closespan=$('<span/>').text('Close');
207 closespan.addClass('ui-icon');
207 closespan.addClass('ui-icon');
208 closespan.addClass('ui-icon-close');
208 closespan.addClass('ui-icon-close');
209 closelink.append(closespan);
209 closelink.append(closespan);
210 closelink.click(function(){
210 closelink.click(function(){
211 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
211 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
212 setTimeout(function(){that.code_mirror.focus();}, 50);
212 setTimeout(function(){that.code_mirror.focus();}, 50);
213 });
213 });
214 //construct the tooltip
214 //construct the tooltip
215 tooltip.append(closelink);
215 tooltip.append(closelink);
216 tooltip.append(expandlink);
216 tooltip.append(expandlink);
217 tooltip.append(morelink);
217 tooltip.append(morelink);
218 if(defstring){
218 if(defstring){
219 defstring_html= $('<pre/>').html(utils.fixConsole(defstring));
219 defstring_html= $('<pre/>').html(utils.fixConsole(defstring));
220 tooltip.append(defstring_html);
220 tooltip.append(defstring_html);
221 }
221 }
222 tooltip.append(pre);
222 tooltip.append(pre);
223 var pos = this.code_mirror.cursorCoords();
223 var pos = this.code_mirror.cursorCoords();
224 tooltip.css('left',pos.x+'px');
224 tooltip.css('left',pos.x+'px');
225 tooltip.css('top',pos.yBot+'px');
225 tooltip.css('top',pos.yBot+'px');
226 $('body').append(tooltip);
226 $('body').append(tooltip);
227
227
228 // issues with cross-closing if multiple tooltip in less than 5sec
228 // issues with cross-closing if multiple tooltip in less than 5sec
229 // keep it comented for now
229 // keep it comented for now
230 // setTimeout(CodeCell.prototype.remove_and_cancell_tooltip, 5000);
230 // setTimeout(CodeCell.prototype.remove_and_cancell_tooltip, 5000);
231 };
231 };
232
232
233 // As you type completer
233 // As you type completer
234 CodeCell.prototype.finish_completing = function (matched_text, matches) {
234 CodeCell.prototype.finish_completing = function (matched_text, matches) {
235 //return if not completing or nothing to complete
235 //return if not completing or nothing to complete
236 if (!this.is_completing || matches.length === 0) {return;}
236 if (!this.is_completing || matches.length === 0) {return;}
237
237
238 // for later readability
238 // for later readability
239 var key = { tab:9,
239 var key = { tab:9,
240 esc:27,
240 esc:27,
241 backspace:8,
241 backspace:8,
242 space:13,
242 space:13,
243 shift:16,
243 shift:16,
244 enter:32,
244 enter:32,
245 // _ is 189
245 // _ is 189
246 isCompSymbol : function (code)
246 isCompSymbol : function (code)
247 {return ((code>64 && code <=122)|| code == 189)}
247 {return ((code>64 && code <=122)|| code == 189)}
248 }
248 }
249
249
250 // smart completion, sort kwarg ending with '='
250 // smart completion, sort kwarg ending with '='
251 var newm = new Array();
251 var newm = new Array();
252 if(this.notebook.smart_completer)
252 if(this.notebook.smart_completer)
253 {
253 {
254 kwargs = new Array();
254 kwargs = new Array();
255 other = new Array();
255 other = new Array();
256 for(var i=0;i<matches.length; ++i){
256 for(var i=0;i<matches.length; ++i){
257 if(matches[i].substr(-1) === '='){
257 if(matches[i].substr(-1) === '='){
258 kwargs.push(matches[i]);
258 kwargs.push(matches[i]);
259 }else{other.push(matches[i]);}
259 }else{other.push(matches[i]);}
260 }
260 }
261 newm = kwargs.concat(other);
261 newm = kwargs.concat(other);
262 matches=newm;
262 matches=newm;
263 }
263 }
264 // end sort kwargs
264 // end sort kwargs
265
265
266 // give common prefix of a array of string
266 // give common prefix of a array of string
267 function sharedStart(A){
267 function sharedStart(A){
268 if(A.length > 1 ){
268 if(A.length > 1 ){
269 var tem1, tem2, s, A= A.slice(0).sort();
269 var tem1, tem2, s, A= A.slice(0).sort();
270 tem1= A[0];
270 tem1= A[0];
271 s= tem1.length;
271 s= tem1.length;
272 tem2= A.pop();
272 tem2= A.pop();
273 while(s && tem2.indexOf(tem1)== -1){
273 while(s && tem2.indexOf(tem1)== -1){
274 tem1= tem1.substring(0, --s);
274 tem1= tem1.substring(0, --s);
275 }
275 }
276 return tem1;
276 return tem1;
277 }
277 }
278 return "";
278 return "";
279 }
279 }
280
280
281
281
282 //try to check if the user is typing tab at least twice after a word
282 //try to check if the user is typing tab at least twice after a word
283 // and completion is "done"
283 // and completion is "done"
284 fallback_on_tooltip_after=2
284 fallback_on_tooltip_after=2
285 if(matches.length==1 && matched_text === matches[0])
285 if(matches.length==1 && matched_text === matches[0])
286 {
286 {
287 if(this.npressed >fallback_on_tooltip_after && this.prevmatch==matched_text)
287 if(this.npressed >fallback_on_tooltip_after && this.prevmatch==matched_text)
288 {
288 {
289 console.log('Ok, you really want to complete after pressing tab '+this.npressed+' times !');
289 console.log('Ok, you really want to complete after pressing tab '+this.npressed+' times !');
290 console.log('You should understand that there is no (more) completion for that !');
290 console.log('You should understand that there is no (more) completion for that !');
291 console.log("I'll show you the tooltip, will you stop bothering me ?");
291 console.log("I'll show you the tooltip, will you stop bothering me ?");
292 this.request_tooltip_after_time(matched_text+'(',0,this);
292 this.request_tooltip_after_time(matched_text+'(',0,this);
293 return;
293 return;
294 }
294 }
295 this.prevmatch=matched_text
295 this.prevmatch=matched_text
296 this.npressed=this.npressed+1;
296 this.npressed=this.npressed+1;
297 }
297 }
298 else
298 else
299 {
299 {
300 this.prevmatch="";
300 this.prevmatch="";
301 this.npressed=0;
301 this.npressed=0;
302 }
302 }
303 // end fallback on tooltip
303 // end fallback on tooltip
304 //==================================
304 //==================================
305 // Real completion logic start here
305 // Real completion logic start here
306 var that = this;
306 var that = this;
307 var cur = this.completion_cursor;
307 var cur = this.completion_cursor;
308 var done = false;
308 var done = false;
309
309
310 // call to dismmiss the completer
310 // call to dismmiss the completer
311 var close = function () {
311 var close = function () {
312 if (done) return;
312 if (done) return;
313 done = true;
313 done = true;
314 if (complete!=undefined)
314 if (complete!=undefined)
315 {complete.remove();}
315 {complete.remove();}
316 that.is_completing = false;
316 that.is_completing = false;
317 that.completion_cursor = null;
317 that.completion_cursor = null;
318 };
318 };
319
319
320 // insert the given text and exit the completer
320 // insert the given text and exit the completer
321 var insert = function (selected_text, event) {
321 var insert = function (selected_text, event) {
322 that.code_mirror.replaceRange(
322 that.code_mirror.replaceRange(
323 selected_text,
323 selected_text,
324 {line: cur.line, ch: (cur.ch-matched_text.length)},
324 {line: cur.line, ch: (cur.ch-matched_text.length)},
325 {line: cur.line, ch: cur.ch}
325 {line: cur.line, ch: cur.ch}
326 );
326 );
327 if(event != null){
327 if(event != null){
328 event.stopPropagation();
328 event.stopPropagation();
329 event.preventDefault();
329 event.preventDefault();
330 }
330 }
331 close();
331 close();
332 setTimeout(function(){that.code_mirror.focus();}, 50);
332 setTimeout(function(){that.code_mirror.focus();}, 50);
333 };
333 };
334
334
335 // insert the curent highlited selection and exit
335 // insert the curent highlited selection and exit
336 var pick = function () {
336 var pick = function () {
337 insert(select.val()[0],null);
337 insert(select.val()[0],null);
338 };
338 };
339
339
340
340
341 // Define function to clear the completer, refill it with the new
341 // Define function to clear the completer, refill it with the new
342 // matches, update the pseuso typing field. autopick insert match if
342 // matches, update the pseuso typing field. autopick insert match if
343 // only one left, in no matches (anymore) dismiss itself by pasting
343 // only one left, in no matches (anymore) dismiss itself by pasting
344 // what the user have typed until then
344 // what the user have typed until then
345 var complete_with = function(matches,typed_text,autopick,event)
345 var complete_with = function(matches,typed_text,autopick,event)
346 {
346 {
347 // If autopick an only one match, past.
347 // If autopick an only one match, past.
348 // Used to 'pick' when pressing tab
348 // Used to 'pick' when pressing tab
349 if (matches.length < 1) {
349 if (matches.length < 1) {
350 insert(typed_text,event);
350 insert(typed_text,event);
351 if(event !=null){
351 if(event !=null){
352 event.stopPropagation();
352 event.stopPropagation();
353 event.preventDefault();
353 event.preventDefault();
354 }
354 }
355 } else if (autopick && matches.length==1) {
355 } else if (autopick && matches.length==1) {
356 insert(matches[0],event);
356 insert(matches[0],event);
357 if(event !=null){
357 if(event !=null){
358 event.stopPropagation();
358 event.stopPropagation();
359 event.preventDefault();
359 event.preventDefault();
360 }
360 }
361 }
361 }
362 //clear the previous completion if any
362 //clear the previous completion if any
363 complete.children().children().remove();
363 complete.children().children().remove();
364 $('#asyoutype').text(typed_text);
364 $('#asyoutype').text(typed_text);
365 select=$('#asyoutypeselect');
365 select=$('#asyoutypeselect');
366 for (var i=0; i<matches.length; ++i) {
366 for (var i=0; i<matches.length; ++i) {
367 select.append($('<option/>').html(matches[i]));
367 select.append($('<option/>').html(matches[i]));
368 }
368 }
369 select.children().first().attr('selected','true');
369 select.children().first().attr('selected','true');
370 }
370 }
371
371
372 // create html for completer
372 // create html for completer
373 var complete = $('<div/>').addClass('completions');
373 var complete = $('<div/>').addClass('completions');
374 complete.attr('id','complete');
374 complete.attr('id','complete');
375 complete.append($('<p/>').attr('id', 'asyoutype').html(matched_text));//pseudo input field
375 complete.append($('<p/>').attr('id', 'asyoutype').html(matched_text));//pseudo input field
376
376
377 var select = $('<select/>').attr('multiple','true');
377 var select = $('<select/>').attr('multiple','true');
378 select.attr('id', 'asyoutypeselect')
378 select.attr('id', 'asyoutypeselect')
379 select.attr('size',Math.min(10,matches.length));
379 select.attr('size',Math.min(10,matches.length));
380 var pos = this.code_mirror.cursorCoords();
380 var pos = this.code_mirror.cursorCoords();
381
381
382 // TODO: I propose to remove enough horizontal pixel
382 // TODO: I propose to remove enough horizontal pixel
383 // to align the text later
383 // to align the text later
384 complete.css('left',pos.x+'px');
384 complete.css('left',pos.x+'px');
385 complete.css('top',pos.yBot+'px');
385 complete.css('top',pos.yBot+'px');
386 complete.append(select);
386 complete.append(select);
387
387
388 $('body').append(complete);
388 $('body').append(complete);
389
389
390 // So a first actual completion. see if all the completion start wit
390 // So a first actual completion. see if all the completion start wit
391 // the same letter and complete if necessary
391 // the same letter and complete if necessary
392 fastForward = sharedStart(matches)
392 fastForward = sharedStart(matches)
393 typed_characters= fastForward.substr(matched_text.length);
393 typed_characters= fastForward.substr(matched_text.length);
394 complete_with(matches,matched_text+typed_characters,true,null);
394 complete_with(matches,matched_text+typed_characters,true,null);
395 filterd=matches;
395 filterd=matches;
396 // Give focus to select, and make it filter the match as the user type
396 // Give focus to select, and make it filter the match as the user type
397 // by filtering the previous matches. Called by .keypress and .keydown
397 // by filtering the previous matches. Called by .keypress and .keydown
398 var downandpress = function (event,press_or_down) {
398 var downandpress = function (event,press_or_down) {
399 var code = event.which;
399 var code = event.which;
400 var autopick = false; // auto 'pick' if only one match
400 var autopick = false; // auto 'pick' if only one match
401 if (press_or_down === 0){
401 if (press_or_down === 0){
402 press=true; down=false; //Are we called from keypress or keydown
402 press=true; down=false; //Are we called from keypress or keydown
403 } else if (press_or_down == 1){
403 } else if (press_or_down == 1){
404 press=false; down=true;
404 press=false; down=true;
405 }
405 }
406 if (code === key.shift) {
406 if (code === key.shift) {
407 // nothing on Shift
407 // nothing on Shift
408 return;
408 return;
409 }
409 }
410 if (code === key.space || code === key.enter) {
410 if (code === key.space || code === key.enter) {
411 // Pressing SPACE or ENTER will cause a pick
411 // Pressing SPACE or ENTER will cause a pick
412 event.stopPropagation();
412 event.stopPropagation();
413 event.preventDefault();
413 event.preventDefault();
414 pick();
414 pick();
415 } else if (code === 38 || code === 40) {
415 } else if (code === 38 || code === 40) {
416 // We don't want the document keydown handler to handle UP/DOWN,
416 // We don't want the document keydown handler to handle UP/DOWN,
417 // but we want the default action.
417 // but we want the default action.
418 event.stopPropagation();
418 event.stopPropagation();
419 //} else if ( key.isCompSymbol(code)|| (code==key.backspace)||(code==key.tab && down)){
419 //} else if ( key.isCompSymbol(code)|| (code==key.backspace)||(code==key.tab && down)){
420 } else if ( (code==key.backspace)||(code==key.tab && down) || press || key.isCompSymbol(code)){
420 } else if ( (code==key.backspace)||(code==key.tab && down) || press || key.isCompSymbol(code)){
421 if( key.isCompSymbol(code) && press)
421 if( key.isCompSymbol(code) && press)
422 {
422 {
423 var newchar = String.fromCharCode(code);
423 var newchar = String.fromCharCode(code);
424 typed_characters=typed_characters+newchar;
424 typed_characters=typed_characters+newchar;
425 } else if (code == key.tab) {
425 } else if (code == key.tab) {
426 fastForward = sharedStart(filterd)
426 fastForward = sharedStart(filterd)
427 ffsub = fastForward.substr(matched_text.length+typed_characters.length);
427 ffsub = fastForward.substr(matched_text.length+typed_characters.length);
428 typed_characters=typed_characters+ffsub;
428 typed_characters=typed_characters+ffsub;
429 autopick=true;
429 autopick=true;
430 event.stopPropagation();
430 event.stopPropagation();
431 event.preventDefault();
431 event.preventDefault();
432 } else if (code == key.backspace && down) {
432 } else if (code == key.backspace && down) {
433 // cancel if user have erase everything, otherwise decrease
433 // cancel if user have erase everything, otherwise decrease
434 // what we filter with
434 // what we filter with
435 if (typed_characters.length <= 0)
435 if (typed_characters.length <= 0)
436 {
436 {
437 insert(matched_text,event)
437 insert(matched_text,event)
438 }
438 }
439 typed_characters=typed_characters.substr(0,typed_characters.length-1);
439 typed_characters=typed_characters.substr(0,typed_characters.length-1);
440 }else{return}
440 }else{return}
441 re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
441 re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
442 filterd = matches.filter(function(x){return re.test(x)});
442 filterd = matches.filter(function(x){return re.test(x)});
443 complete_with(filterd,matched_text+typed_characters,autopick,event);
443 complete_with(filterd,matched_text+typed_characters,autopick,event);
444 } else if(down){ // abort only on .keydown
444 } else if(down){ // abort only on .keydown
445 // abort with what the user have pressed until now
445 // abort with what the user have pressed until now
446 console.log('aborting with keycode : '+code+' is down :'+down);
446 console.log('aborting with keycode : '+code+' is down :'+down);
447 insert(matched_text+typed_characters,event);
447 insert(matched_text+typed_characters,event);
448 }
448 }
449 }
449 }
450 select.keydown(function (event) {
450 select.keydown(function (event) {
451 downandpress(event,1)
451 downandpress(event,1)
452 });
452 });
453 select.keypress(function (event) {
453 select.keypress(function (event) {
454 downandpress(event,0)
454 downandpress(event,0)
455 });
455 });
456 // Double click also causes a pick.
456 // Double click also causes a pick.
457 // and bind the last actions.
457 // and bind the last actions.
458 select.dblclick(pick);
458 select.dblclick(pick);
459 select.blur(close);
459 select.blur(close);
460 select.focus();
460 select.focus();
461 };
461 };
462
462
463 CodeCell.prototype.toggle_line_numbers = function () {
463 CodeCell.prototype.toggle_line_numbers = function () {
464 if (this.code_mirror.getOption('lineNumbers') == false) {
464 if (this.code_mirror.getOption('lineNumbers') == false) {
465 this.code_mirror.setOption('lineNumbers', true);
465 this.code_mirror.setOption('lineNumbers', true);
466 } else {
466 } else {
467 this.code_mirror.setOption('lineNumbers', false);
467 this.code_mirror.setOption('lineNumbers', false);
468 }
468 }
469 this.code_mirror.refresh();
469 this.code_mirror.refresh();
470 };
470 };
471
471
472 CodeCell.prototype.select = function () {
472 CodeCell.prototype.select = function () {
473 IPython.Cell.prototype.select.apply(this);
473 IPython.Cell.prototype.select.apply(this);
474 // Todo: this dance is needed because as of CodeMirror 2.12, focus is
474 // Todo: this dance is needed because as of CodeMirror 2.12, focus is
475 // not causing the cursor to blink if the editor is empty initially.
475 // not causing the cursor to blink if the editor is empty initially.
476 // While this seems to fix the issue, this should be fixed
476 // While this seems to fix the issue, this should be fixed
477 // in CodeMirror proper.
477 // in CodeMirror proper.
478 var s = this.code_mirror.getValue();
478 var s = this.code_mirror.getValue();
479 this.code_mirror.focus();
479 this.code_mirror.focus();
480 if (s === '') this.code_mirror.setValue('');
480 if (s === '') this.code_mirror.setValue('');
481 };
481 };
482
482
483
483
484 CodeCell.prototype.select_all = function () {
484 CodeCell.prototype.select_all = function () {
485 var start = {line: 0, ch: 0};
485 var start = {line: 0, ch: 0};
486 var nlines = this.code_mirror.lineCount();
486 var nlines = this.code_mirror.lineCount();
487 var last_line = this.code_mirror.getLine(nlines-1);
487 var last_line = this.code_mirror.getLine(nlines-1);
488 var end = {line: nlines-1, ch: last_line.length};
488 var end = {line: nlines-1, ch: last_line.length};
489 this.code_mirror.setSelection(start, end);
489 this.code_mirror.setSelection(start, end);
490 };
490 };
491
491
492
492
493 CodeCell.prototype.append_output = function (json) {
493 CodeCell.prototype.append_output = function (json) {
494 this.expand();
494 this.expand();
495 if (json.output_type === 'pyout') {
495 if (json.output_type === 'pyout') {
496 this.append_pyout(json);
496 this.append_pyout(json);
497 } else if (json.output_type === 'pyerr') {
497 } else if (json.output_type === 'pyerr') {
498 this.append_pyerr(json);
498 this.append_pyerr(json);
499 } else if (json.output_type === 'display_data') {
499 } else if (json.output_type === 'display_data') {
500 this.append_display_data(json);
500 this.append_display_data(json);
501 } else if (json.output_type === 'stream') {
501 } else if (json.output_type === 'stream') {
502 this.append_stream(json);
502 this.append_stream(json);
503 };
503 };
504 this.outputs.push(json);
504 this.outputs.push(json);
505 };
505 };
506
506
507
507
508 CodeCell.prototype.create_output_area = function () {
508 CodeCell.prototype.create_output_area = function () {
509 var oa = $("<div/>").addClass("hbox output_area");
509 var oa = $("<div/>").addClass("hbox output_area");
510 oa.append($('<div/>').addClass('prompt'));
510 oa.append($('<div/>').addClass('prompt'));
511 return oa;
511 return oa;
512 };
512 };
513
513
514
514
515 CodeCell.prototype.append_pyout = function (json) {
515 CodeCell.prototype.append_pyout = function (json) {
516 n = json.prompt_number || ' ';
516 n = json.prompt_number || ' ';
517 var toinsert = this.create_output_area();
517 var toinsert = this.create_output_area();
518 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
518 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
519 this.append_mime_type(json, toinsert);
519 this.append_mime_type(json, toinsert);
520 this.element.find('div.output').append(toinsert);
520 this.element.find('div.output').append(toinsert);
521 // If we just output latex, typeset it.
521 // If we just output latex, typeset it.
522 if ((json.latex !== undefined) || (json.html !== undefined)) {
522 if ((json.latex !== undefined) || (json.html !== undefined)) {
523 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
523 this.typeset();
524 };
524 };
525 };
525 };
526
526
527
527
528 CodeCell.prototype.append_pyerr = function (json) {
528 CodeCell.prototype.append_pyerr = function (json) {
529 var tb = json.traceback;
529 var tb = json.traceback;
530 if (tb !== undefined && tb.length > 0) {
530 if (tb !== undefined && tb.length > 0) {
531 var s = '';
531 var s = '';
532 var len = tb.length;
532 var len = tb.length;
533 for (var i=0; i<len; i++) {
533 for (var i=0; i<len; i++) {
534 s = s + tb[i] + '\n';
534 s = s + tb[i] + '\n';
535 }
535 }
536 s = s + '\n';
536 s = s + '\n';
537 var toinsert = this.create_output_area();
537 var toinsert = this.create_output_area();
538 this.append_text(s, toinsert);
538 this.append_text(s, toinsert);
539 this.element.find('div.output').append(toinsert);
539 this.element.find('div.output').append(toinsert);
540 };
540 };
541 };
541 };
542
542
543
543
544 CodeCell.prototype.append_stream = function (json) {
544 CodeCell.prototype.append_stream = function (json) {
545 // temporary fix: if stream undefined (json file written prior to this patch),
545 // temporary fix: if stream undefined (json file written prior to this patch),
546 // default to most likely stdout:
546 // default to most likely stdout:
547 if (json.stream == undefined){
547 if (json.stream == undefined){
548 json.stream = 'stdout';
548 json.stream = 'stdout';
549 }
549 }
550 var subclass = "output_"+json.stream;
550 var subclass = "output_"+json.stream;
551 if (this.outputs.length > 0){
551 if (this.outputs.length > 0){
552 // have at least one output to consider
552 // have at least one output to consider
553 var last = this.outputs[this.outputs.length-1];
553 var last = this.outputs[this.outputs.length-1];
554 if (last.output_type == 'stream' && json.stream == last.stream){
554 if (last.output_type == 'stream' && json.stream == last.stream){
555 // latest output was in the same stream,
555 // latest output was in the same stream,
556 // so append directly into its pre tag
556 // so append directly into its pre tag
557 this.element.find('div.'+subclass).last().find('pre').append(json.text);
557 this.element.find('div.'+subclass).last().find('pre').append(json.text);
558 return;
558 return;
559 }
559 }
560 }
560 }
561
561
562 // If we got here, attach a new div
562 // If we got here, attach a new div
563 var toinsert = this.create_output_area();
563 var toinsert = this.create_output_area();
564 this.append_text(json.text, toinsert, "output_stream "+subclass);
564 this.append_text(json.text, toinsert, "output_stream "+subclass);
565 this.element.find('div.output').append(toinsert);
565 this.element.find('div.output').append(toinsert);
566 };
566 };
567
567
568
568
569 CodeCell.prototype.append_display_data = function (json) {
569 CodeCell.prototype.append_display_data = function (json) {
570 var toinsert = this.create_output_area();
570 var toinsert = this.create_output_area();
571 this.append_mime_type(json, toinsert);
571 this.append_mime_type(json, toinsert);
572 this.element.find('div.output').append(toinsert);
572 this.element.find('div.output').append(toinsert);
573 // If we just output latex, typeset it.
573 // If we just output latex, typeset it.
574 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
574 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
575 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
575 this.typeset();
576 };
576 };
577 };
577 };
578
578
579
579
580 CodeCell.prototype.append_mime_type = function (json, element) {
580 CodeCell.prototype.append_mime_type = function (json, element) {
581 if (json.html !== undefined) {
581 if (json.html !== undefined) {
582 this.append_html(json.html, element);
582 this.append_html(json.html, element);
583 } else if (json.latex !== undefined) {
583 } else if (json.latex !== undefined) {
584 this.append_latex(json.latex, element);
584 this.append_latex(json.latex, element);
585 } else if (json.svg !== undefined) {
585 } else if (json.svg !== undefined) {
586 this.append_svg(json.svg, element);
586 this.append_svg(json.svg, element);
587 } else if (json.png !== undefined) {
587 } else if (json.png !== undefined) {
588 this.append_png(json.png, element);
588 this.append_png(json.png, element);
589 } else if (json.jpeg !== undefined) {
589 } else if (json.jpeg !== undefined) {
590 this.append_jpeg(json.jpeg, element);
590 this.append_jpeg(json.jpeg, element);
591 } else if (json.text !== undefined) {
591 } else if (json.text !== undefined) {
592 this.append_text(json.text, element);
592 this.append_text(json.text, element);
593 };
593 };
594 };
594 };
595
595
596
596
597 CodeCell.prototype.append_html = function (html, element) {
597 CodeCell.prototype.append_html = function (html, element) {
598 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
598 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
599 toinsert.append(html);
599 toinsert.append(html);
600 element.append(toinsert);
600 element.append(toinsert);
601 };
601 };
602
602
603
603
604 CodeCell.prototype.append_text = function (data, element, extra_class) {
604 CodeCell.prototype.append_text = function (data, element, extra_class) {
605 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
605 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
606 if (extra_class){
606 if (extra_class){
607 toinsert.addClass(extra_class);
607 toinsert.addClass(extra_class);
608 }
608 }
609 toinsert.append($("<pre/>").html(data));
609 toinsert.append($("<pre/>").html(data));
610 element.append(toinsert);
610 element.append(toinsert);
611 };
611 };
612
612
613
613
614 CodeCell.prototype.append_svg = function (svg, element) {
614 CodeCell.prototype.append_svg = function (svg, element) {
615 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
615 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
616 toinsert.append(svg);
616 toinsert.append(svg);
617 element.append(toinsert);
617 element.append(toinsert);
618 };
618 };
619
619
620
620
621 CodeCell.prototype.append_png = function (png, element) {
621 CodeCell.prototype.append_png = function (png, element) {
622 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
622 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
623 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
623 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
624 element.append(toinsert);
624 element.append(toinsert);
625 };
625 };
626
626
627
627
628 CodeCell.prototype.append_jpeg = function (jpeg, element) {
628 CodeCell.prototype.append_jpeg = function (jpeg, element) {
629 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
629 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
630 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
630 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
631 element.append(toinsert);
631 element.append(toinsert);
632 };
632 };
633
633
634
634
635 CodeCell.prototype.append_latex = function (latex, element) {
635 CodeCell.prototype.append_latex = function (latex, element) {
636 // This method cannot do the typesetting because the latex first has to
636 // This method cannot do the typesetting because the latex first has to
637 // be on the page.
637 // be on the page.
638 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
638 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
639 toinsert.append(latex);
639 toinsert.append(latex);
640 element.append(toinsert);
640 element.append(toinsert);
641 };
641 };
642
642
643
643
644 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
644 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
645 var output_div = this.element.find("div.output");
645 var output_div = this.element.find("div.output");
646 if (stdout && stderr && other){
646 if (stdout && stderr && other){
647 // clear all, no need for logic
647 // clear all, no need for logic
648 output_div.html("");
648 output_div.html("");
649 this.outputs = [];
649 this.outputs = [];
650 return;
650 return;
651 }
651 }
652 // remove html output
652 // remove html output
653 // each output_subarea that has an identifying class is in an output_area
653 // each output_subarea that has an identifying class is in an output_area
654 // which is the element to be removed.
654 // which is the element to be removed.
655 if (stdout){
655 if (stdout){
656 output_div.find("div.output_stdout").parent().remove();
656 output_div.find("div.output_stdout").parent().remove();
657 }
657 }
658 if (stderr){
658 if (stderr){
659 output_div.find("div.output_stderr").parent().remove();
659 output_div.find("div.output_stderr").parent().remove();
660 }
660 }
661 if (other){
661 if (other){
662 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
662 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
663 }
663 }
664
664
665 // remove cleared outputs from JSON list:
665 // remove cleared outputs from JSON list:
666 for (var i = this.outputs.length - 1; i >= 0; i--){
666 for (var i = this.outputs.length - 1; i >= 0; i--){
667 var out = this.outputs[i];
667 var out = this.outputs[i];
668 var output_type = out.output_type;
668 var output_type = out.output_type;
669 if (output_type == "display_data" && other){
669 if (output_type == "display_data" && other){
670 this.outputs.splice(i,1);
670 this.outputs.splice(i,1);
671 }else if (output_type == "stream"){
671 }else if (output_type == "stream"){
672 if (stdout && out.stream == "stdout"){
672 if (stdout && out.stream == "stdout"){
673 this.outputs.splice(i,1);
673 this.outputs.splice(i,1);
674 }else if (stderr && out.stream == "stderr"){
674 }else if (stderr && out.stream == "stderr"){
675 this.outputs.splice(i,1);
675 this.outputs.splice(i,1);
676 }
676 }
677 }
677 }
678 }
678 }
679 };
679 };
680
680
681
681
682 CodeCell.prototype.clear_input = function () {
682 CodeCell.prototype.clear_input = function () {
683 this.code_mirror.setValue('');
683 this.code_mirror.setValue('');
684 };
684 };
685
685
686
686
687 CodeCell.prototype.collapse = function () {
687 CodeCell.prototype.collapse = function () {
688 if (!this.collapsed) {
688 if (!this.collapsed) {
689 this.element.find('div.output').hide();
689 this.element.find('div.output').hide();
690 this.collapsed = true;
690 this.collapsed = true;
691 };
691 };
692 };
692 };
693
693
694
694
695 CodeCell.prototype.expand = function () {
695 CodeCell.prototype.expand = function () {
696 if (this.collapsed) {
696 if (this.collapsed) {
697 this.element.find('div.output').show();
697 this.element.find('div.output').show();
698 this.collapsed = false;
698 this.collapsed = false;
699 };
699 };
700 };
700 };
701
701
702
702
703 CodeCell.prototype.toggle_output = function () {
703 CodeCell.prototype.toggle_output = function () {
704 if (this.collapsed) {
704 if (this.collapsed) {
705 this.expand();
705 this.expand();
706 } else {
706 } else {
707 this.collapse();
707 this.collapse();
708 };
708 };
709 };
709 };
710
710
711 CodeCell.prototype.set_input_prompt = function (number) {
711 CodeCell.prototype.set_input_prompt = function (number) {
712 var n = number || '&nbsp;';
712 var n = number || '&nbsp;';
713 this.input_prompt_number = n;
713 this.input_prompt_number = n;
714 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
714 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
715 };
715 };
716
716
717
717
718 CodeCell.prototype.get_code = function () {
718 CodeCell.prototype.get_code = function () {
719 return this.code_mirror.getValue();
719 return this.code_mirror.getValue();
720 };
720 };
721
721
722
722
723 CodeCell.prototype.set_code = function (code) {
723 CodeCell.prototype.set_code = function (code) {
724 return this.code_mirror.setValue(code);
724 return this.code_mirror.setValue(code);
725 };
725 };
726
726
727
727
728 CodeCell.prototype.at_top = function () {
728 CodeCell.prototype.at_top = function () {
729 var cursor = this.code_mirror.getCursor();
729 var cursor = this.code_mirror.getCursor();
730 if (cursor.line === 0) {
730 if (cursor.line === 0) {
731 return true;
731 return true;
732 } else {
732 } else {
733 return false;
733 return false;
734 }
734 }
735 };
735 };
736
736
737
737
738 CodeCell.prototype.at_bottom = function () {
738 CodeCell.prototype.at_bottom = function () {
739 var cursor = this.code_mirror.getCursor();
739 var cursor = this.code_mirror.getCursor();
740 if (cursor.line === (this.code_mirror.lineCount()-1)) {
740 if (cursor.line === (this.code_mirror.lineCount()-1)) {
741 return true;
741 return true;
742 } else {
742 } else {
743 return false;
743 return false;
744 }
744 }
745 };
745 };
746
746
747
747
748 CodeCell.prototype.fromJSON = function (data) {
748 CodeCell.prototype.fromJSON = function (data) {
749 console.log('Import from JSON:', data);
749 console.log('Import from JSON:', data);
750 if (data.cell_type === 'code') {
750 if (data.cell_type === 'code') {
751 if (data.input !== undefined) {
751 if (data.input !== undefined) {
752 this.set_code(data.input);
752 this.set_code(data.input);
753 }
753 }
754 if (data.prompt_number !== undefined) {
754 if (data.prompt_number !== undefined) {
755 this.set_input_prompt(data.prompt_number);
755 this.set_input_prompt(data.prompt_number);
756 } else {
756 } else {
757 this.set_input_prompt();
757 this.set_input_prompt();
758 };
758 };
759 var len = data.outputs.length;
759 var len = data.outputs.length;
760 for (var i=0; i<len; i++) {
760 for (var i=0; i<len; i++) {
761 this.append_output(data.outputs[i]);
761 this.append_output(data.outputs[i]);
762 };
762 };
763 if (data.collapsed !== undefined) {
763 if (data.collapsed !== undefined) {
764 if (data.collapsed) {
764 if (data.collapsed) {
765 this.collapse();
765 this.collapse();
766 };
766 };
767 };
767 };
768 };
768 };
769 };
769 };
770
770
771
771
772 CodeCell.prototype.toJSON = function () {
772 CodeCell.prototype.toJSON = function () {
773 var data = {};
773 var data = {};
774 data.input = this.get_code();
774 data.input = this.get_code();
775 data.cell_type = 'code';
775 data.cell_type = 'code';
776 if (this.input_prompt_number !== ' ') {
776 if (this.input_prompt_number !== ' ') {
777 data.prompt_number = this.input_prompt_number;
777 data.prompt_number = this.input_prompt_number;
778 };
778 };
779 var outputs = [];
779 var outputs = [];
780 var len = this.outputs.length;
780 var len = this.outputs.length;
781 for (var i=0; i<len; i++) {
781 for (var i=0; i<len; i++) {
782 outputs[i] = this.outputs[i];
782 outputs[i] = this.outputs[i];
783 };
783 };
784 data.outputs = outputs;
784 data.outputs = outputs;
785 data.language = 'python';
785 data.language = 'python';
786 data.collapsed = this.collapsed;
786 data.collapsed = this.collapsed;
787 // console.log('Export to JSON:',data);
787 // console.log('Export to JSON:',data);
788 return data;
788 return data;
789 };
789 };
790
790
791
791
792 IPython.CodeCell = CodeCell;
792 IPython.CodeCell = CodeCell;
793
793
794 return IPython;
794 return IPython;
795 }(IPython));
795 }(IPython));
@@ -1,85 +1,131 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // On document ready
9 // On document ready
10 //============================================================================
10 //============================================================================
11
11
12
12
13 $(document).ready(function () {
13 $(document).ready(function () {
14
14
15 MathJax.Hub.Config({
15 if (window.MathJax == undefined){
16 tex2jax: {
16 // MathJax undefined, but expected. Draw warning.
17 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
17 window.MathJax = null;
18 displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
18 var dialog = $('<div></div>').html(
19 },
19 "<p class='dialog'>"+
20 displayAlign: 'left', // Change this to 'center' to center equations.
20 "We were unable to retrieve MathJax. Math/LaTeX rendering will be disabled."+
21 "HTML-CSS": {
21 "</p>"+
22 styles: {'.MathJax_Display': {"margin": 0}}
22 "<p class='dialog'>"+
23 }
23 "With a working internet connection, you can run the following at a Python"+
24 });
24 " or IPython prompt, which will install a local copy of MathJax:"+
25 "</p>"+
26 "<pre class='dialog'>"+
27 ">>> from IPython.external import mathjax; mathjax.install_mathjax()"+
28 "</pre>"+
29 "<p class='dialog'>"+
30 "This will try to install MathJax into the directory where you installed"+
31 " IPython. If you installed IPython to a location that requires"+
32 " administrative privileges to write, you will need to make this call as"+
33 " an administrator."+
34 "</p>"+
35 "<p class='dialog'>"+
36 "On OSX/Linux/Unix, this can be done at the command-line via:"+
37 "</p>"+
38 "<pre class='dialog'>"+
39 "$ sudo python -c 'from IPython.external import mathjax; mathjax.install_mathjax()'"+
40 "</pre>"+
41 "<p class='dialog'>"+
42 "Or you can instruct the notebook server to start without MathJax support, with:"+
43 "<pre class='dialog'>"+
44 "</p>"+
45 "$ ipython notebook --no-mathjax"+
46 "</pre>"+
47 "<p class='dialog'>"+
48 "in which case, equations will not be rendered."+
49 "</p>"
50 ).dialog({
51 title: 'MathJax disabled',
52 width: "70%",
53 modal: true,
54 })
55 }else if (window.MathJax){
56 MathJax.Hub.Config({
57 tex2jax: {
58 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
59 displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
60 },
61 displayAlign: 'left', // Change this to 'center' to center equations.
62 "HTML-CSS": {
63 styles: {'.MathJax_Display': {"margin": 0}}
64 }
65 });
66 }else{
67 // window.MathJax == null
68 // --no-mathjax mode
69 }
70
25 IPython.markdown_converter = new Markdown.Converter();
71 IPython.markdown_converter = new Markdown.Converter();
26 IPython.read_only = $('meta[name=read_only]').attr("content") == 'True';
72 IPython.read_only = $('meta[name=read_only]').attr("content") == 'True';
27
73
28 $('div#header').addClass('border-box-sizing');
74 $('div#header').addClass('border-box-sizing');
29 $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content');
75 $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content');
30 $('div#notebook_panel').addClass('border-box-sizing ui-widget');
76 $('div#notebook_panel').addClass('border-box-sizing ui-widget');
31
77
32 IPython.layout_manager = new IPython.LayoutManager();
78 IPython.layout_manager = new IPython.LayoutManager();
33 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
79 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
34 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
80 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
35 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
81 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
36 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
82 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
37 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
83 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
38 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
84 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
39 IPython.notebook = new IPython.Notebook('div#notebook');
85 IPython.notebook = new IPython.Notebook('div#notebook');
40 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
86 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
41 IPython.kernel_status_widget.status_idle();
87 IPython.kernel_status_widget.status_idle();
42
88
43 IPython.layout_manager.do_resize();
89 IPython.layout_manager.do_resize();
44
90
45 // These have display: none in the css file and are made visible here to prevent FLOUC.
91 // These have display: none in the css file and are made visible here to prevent FLOUC.
46 $('div#header').css('display','block');
92 $('div#header').css('display','block');
47
93
48 if(IPython.read_only){
94 if(IPython.read_only){
49 // hide various elements from read-only view
95 // hide various elements from read-only view
50 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
96 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
51 IPython.quick_help.element.addClass('hidden'); // shortcuts are disabled in read_only
97 IPython.quick_help.element.addClass('hidden'); // shortcuts are disabled in read_only
52 $('button#new_notebook').addClass('hidden');
98 $('button#new_notebook').addClass('hidden');
53 $('div#cell_section').addClass('hidden');
99 $('div#cell_section').addClass('hidden');
54 $('div#config_section').addClass('hidden');
100 $('div#config_section').addClass('hidden');
55 $('div#kernel_section').addClass('hidden');
101 $('div#kernel_section').addClass('hidden');
56 $('span#login_widget').removeClass('hidden');
102 $('span#login_widget').removeClass('hidden');
57 // left panel starts collapsed, but the collapse must happen after
103 // left panel starts collapsed, but the collapse must happen after
58 // elements start drawing. Don't draw contents of the panel until
104 // elements start drawing. Don't draw contents of the panel until
59 // after they are collapsed
105 // after they are collapsed
60 IPython.left_panel.left_panel_element.css('visibility', 'hidden');
106 IPython.left_panel.left_panel_element.css('visibility', 'hidden');
61 }
107 }
62
108
63 $('div#main_app').css('display','block');
109 $('div#main_app').css('display','block');
64
110
65 // Perform these actions after the notebook has been loaded.
111 // Perform these actions after the notebook has been loaded.
66 // We wait 100 milliseconds because the notebook scrolls to the top after a load
112 // We wait 100 milliseconds because the notebook scrolls to the top after a load
67 // is completed and we need to wait for that to mostly finish.
113 // is completed and we need to wait for that to mostly finish.
68 IPython.notebook.load_notebook(function () {
114 IPython.notebook.load_notebook(function () {
69 setTimeout(function () {
115 setTimeout(function () {
70 IPython.save_widget.update_url();
116 IPython.save_widget.update_url();
71 IPython.layout_manager.do_resize();
117 IPython.layout_manager.do_resize();
72 IPython.pager.collapse();
118 IPython.pager.collapse();
73 if(IPython.read_only){
119 if(IPython.read_only){
74 // collapse the left panel on read-only
120 // collapse the left panel on read-only
75 IPython.left_panel.collapse();
121 IPython.left_panel.collapse();
76 // and finally unhide the panel contents after collapse
122 // and finally unhide the panel contents after collapse
77 setTimeout(function(){
123 setTimeout(function(){
78 IPython.left_panel.left_panel_element.css('visibility', 'visible');
124 IPython.left_panel.left_panel_element.css('visibility', 'visible');
79 }, 200);
125 }, 200);
80 }
126 }
81 },100);
127 },100);
82 });
128 });
83
129
84 });
130 });
85
131
@@ -1,272 +1,272 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // TextCell
9 // TextCell
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 // TextCell base class
14 // TextCell base class
15
15
16 var TextCell = function (notebook) {
16 var TextCell = function (notebook) {
17 this.code_mirror_mode = this.code_mirror_mode || 'htmlmixed';
17 this.code_mirror_mode = this.code_mirror_mode || 'htmlmixed';
18 this.placeholder = this.placeholder || '\u0000';
18 this.placeholder = this.placeholder || '\u0000';
19 IPython.Cell.apply(this, arguments);
19 IPython.Cell.apply(this, arguments);
20 this.rendered = false;
20 this.rendered = false;
21 this.cell_type = this.cell_type || 'text';
21 this.cell_type = this.cell_type || 'text';
22 };
22 };
23
23
24
24
25 TextCell.prototype = new IPython.Cell();
25 TextCell.prototype = new IPython.Cell();
26
26
27
27
28 TextCell.prototype.create_element = function () {
28 TextCell.prototype.create_element = function () {
29 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
29 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
30 cell.attr('tabindex','2');
30 cell.attr('tabindex','2');
31 var input_area = $('<div/>').addClass('text_cell_input');
31 var input_area = $('<div/>').addClass('text_cell_input');
32 this.code_mirror = CodeMirror(input_area.get(0), {
32 this.code_mirror = CodeMirror(input_area.get(0), {
33 indentUnit : 4,
33 indentUnit : 4,
34 mode: this.code_mirror_mode,
34 mode: this.code_mirror_mode,
35 theme: 'default',
35 theme: 'default',
36 value: this.placeholder,
36 value: this.placeholder,
37 readOnly: this.read_only
37 readOnly: this.read_only
38 });
38 });
39 // The tabindex=-1 makes this div focusable.
39 // The tabindex=-1 makes this div focusable.
40 var render_area = $('<div/>').addClass('text_cell_render').
40 var render_area = $('<div/>').addClass('text_cell_render').
41 addClass('rendered_html').attr('tabindex','-1');
41 addClass('rendered_html').attr('tabindex','-1');
42 cell.append(input_area).append(render_area);
42 cell.append(input_area).append(render_area);
43 this.element = cell;
43 this.element = cell;
44 };
44 };
45
45
46
46
47 TextCell.prototype.bind_events = function () {
47 TextCell.prototype.bind_events = function () {
48 IPython.Cell.prototype.bind_events.apply(this);
48 IPython.Cell.prototype.bind_events.apply(this);
49 var that = this;
49 var that = this;
50 this.element.keydown(function (event) {
50 this.element.keydown(function (event) {
51 if (event.which === 13) {
51 if (event.which === 13) {
52 if (that.rendered) {
52 if (that.rendered) {
53 that.edit();
53 that.edit();
54 event.preventDefault();
54 event.preventDefault();
55 }
55 }
56 }
56 }
57 });
57 });
58 };
58 };
59
59
60
60
61 TextCell.prototype.select = function () {
61 TextCell.prototype.select = function () {
62 IPython.Cell.prototype.select.apply(this);
62 IPython.Cell.prototype.select.apply(this);
63 var output = this.element.find("div.text_cell_render");
63 var output = this.element.find("div.text_cell_render");
64 output.trigger('focus');
64 output.trigger('focus');
65 };
65 };
66
66
67
67
68 TextCell.prototype.edit = function () {
68 TextCell.prototype.edit = function () {
69 if ( this.read_only ) return;
69 if ( this.read_only ) return;
70 if (this.rendered === true) {
70 if (this.rendered === true) {
71 var text_cell = this.element;
71 var text_cell = this.element;
72 var output = text_cell.find("div.text_cell_render");
72 var output = text_cell.find("div.text_cell_render");
73 output.hide();
73 output.hide();
74 text_cell.find('div.text_cell_input').show();
74 text_cell.find('div.text_cell_input').show();
75 this.code_mirror.focus();
75 this.code_mirror.focus();
76 this.code_mirror.refresh();
76 this.code_mirror.refresh();
77 this.rendered = false;
77 this.rendered = false;
78 if (this.get_source() === this.placeholder) {
78 if (this.get_source() === this.placeholder) {
79 this.set_source('');
79 this.set_source('');
80 }
80 }
81 }
81 }
82 };
82 };
83
83
84
84
85 // Subclasses must define render.
85 // Subclasses must define render.
86 TextCell.prototype.render = function () {};
86 TextCell.prototype.render = function () {};
87
87
88
88
89 TextCell.prototype.config_mathjax = function () {
89 TextCell.prototype.config_mathjax = function () {
90 var text_cell = this.element;
90 var text_cell = this.element;
91 var that = this;
91 var that = this;
92 text_cell.click(function () {
92 text_cell.click(function () {
93 that.edit();
93 that.edit();
94 }).focusout(function () {
94 }).focusout(function () {
95 that.render();
95 that.render();
96 });
96 });
97
97
98 text_cell.trigger("focusout");
98 text_cell.trigger("focusout");
99 };
99 };
100
100
101
101
102 TextCell.prototype.get_source = function() {
102 TextCell.prototype.get_source = function() {
103 return this.code_mirror.getValue();
103 return this.code_mirror.getValue();
104 };
104 };
105
105
106
106
107 TextCell.prototype.set_source = function(text) {
107 TextCell.prototype.set_source = function(text) {
108 this.code_mirror.setValue(text);
108 this.code_mirror.setValue(text);
109 this.code_mirror.refresh();
109 this.code_mirror.refresh();
110 };
110 };
111
111
112
112
113 TextCell.prototype.get_rendered = function() {
113 TextCell.prototype.get_rendered = function() {
114 return this.element.find('div.text_cell_render').html();
114 return this.element.find('div.text_cell_render').html();
115 };
115 };
116
116
117
117
118 TextCell.prototype.set_rendered = function(text) {
118 TextCell.prototype.set_rendered = function(text) {
119 this.element.find('div.text_cell_render').html(text);
119 this.element.find('div.text_cell_render').html(text);
120 };
120 };
121
121
122
122
123 TextCell.prototype.at_top = function () {
123 TextCell.prototype.at_top = function () {
124 if (this.rendered) {
124 if (this.rendered) {
125 return true;
125 return true;
126 } else {
126 } else {
127 return false;
127 return false;
128 }
128 }
129 };
129 };
130
130
131
131
132 TextCell.prototype.at_bottom = function () {
132 TextCell.prototype.at_bottom = function () {
133 if (this.rendered) {
133 if (this.rendered) {
134 return true;
134 return true;
135 } else {
135 } else {
136 return false;
136 return false;
137 }
137 }
138 };
138 };
139
139
140
140
141 TextCell.prototype.fromJSON = function (data) {
141 TextCell.prototype.fromJSON = function (data) {
142 if (data.cell_type === this.cell_type) {
142 if (data.cell_type === this.cell_type) {
143 if (data.source !== undefined) {
143 if (data.source !== undefined) {
144 this.set_source(data.source);
144 this.set_source(data.source);
145 this.set_rendered(data.rendered || '');
145 this.set_rendered(data.rendered || '');
146 this.rendered = false;
146 this.rendered = false;
147 this.render();
147 this.render();
148 }
148 }
149 }
149 }
150 };
150 };
151
151
152
152
153 TextCell.prototype.toJSON = function () {
153 TextCell.prototype.toJSON = function () {
154 var data = {};
154 var data = {};
155 data.cell_type = this.cell_type;
155 data.cell_type = this.cell_type;
156 data.source = this.get_source();
156 data.source = this.get_source();
157 return data;
157 return data;
158 };
158 };
159
159
160
160
161 // HTMLCell
161 // HTMLCell
162
162
163 var HTMLCell = function (notebook) {
163 var HTMLCell = function (notebook) {
164 this.placeholder = "\u0000Type <strong>HTML</strong> and LaTeX: $\\alpha^2$";
164 this.placeholder = "\u0000Type <strong>HTML</strong> and LaTeX: $\\alpha^2$";
165 IPython.TextCell.apply(this, arguments);
165 IPython.TextCell.apply(this, arguments);
166 this.cell_type = 'html';
166 this.cell_type = 'html';
167 };
167 };
168
168
169
169
170 HTMLCell.prototype = new TextCell();
170 HTMLCell.prototype = new TextCell();
171
171
172
172
173 HTMLCell.prototype.render = function () {
173 HTMLCell.prototype.render = function () {
174 if (this.rendered === false) {
174 if (this.rendered === false) {
175 var text = this.get_source();
175 var text = this.get_source();
176 if (text === "") { text = this.placeholder; }
176 if (text === "") { text = this.placeholder; }
177 this.set_rendered(text);
177 this.set_rendered(text);
178 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
178 this.typeset();
179 this.element.find('div.text_cell_input').hide();
179 this.element.find('div.text_cell_input').hide();
180 this.element.find("div.text_cell_render").show();
180 this.element.find("div.text_cell_render").show();
181 this.rendered = true;
181 this.rendered = true;
182 }
182 }
183 };
183 };
184
184
185
185
186 // MarkdownCell
186 // MarkdownCell
187
187
188 var MarkdownCell = function (notebook) {
188 var MarkdownCell = function (notebook) {
189 this.placeholder = "\u0000Type *Markdown* and LaTeX: $\\alpha^2$";
189 this.placeholder = "\u0000Type *Markdown* and LaTeX: $\\alpha^2$";
190 IPython.TextCell.apply(this, arguments);
190 IPython.TextCell.apply(this, arguments);
191 this.cell_type = 'markdown';
191 this.cell_type = 'markdown';
192 };
192 };
193
193
194
194
195 MarkdownCell.prototype = new TextCell();
195 MarkdownCell.prototype = new TextCell();
196
196
197
197
198 MarkdownCell.prototype.render = function () {
198 MarkdownCell.prototype.render = function () {
199 if (this.rendered === false) {
199 if (this.rendered === false) {
200 var text = this.get_source();
200 var text = this.get_source();
201 if (text === "") { text = this.placeholder; }
201 if (text === "") { text = this.placeholder; }
202 var html = IPython.markdown_converter.makeHtml(text);
202 var html = IPython.markdown_converter.makeHtml(text);
203 this.set_rendered(html);
203 this.set_rendered(html);
204 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
204 this.typeset()
205 this.element.find('div.text_cell_input').hide();
205 this.element.find('div.text_cell_input').hide();
206 this.element.find("div.text_cell_render").show();
206 this.element.find("div.text_cell_render").show();
207 var code_snippets = this.element.find("pre > code");
207 var code_snippets = this.element.find("pre > code");
208 code_snippets.replaceWith(function () {
208 code_snippets.replaceWith(function () {
209 var code = $(this).html();
209 var code = $(this).html();
210 /* Substitute br for newlines and &nbsp; for spaces
210 /* Substitute br for newlines and &nbsp; for spaces
211 before highlighting, since prettify doesn't
211 before highlighting, since prettify doesn't
212 preserve those on all browsers */
212 preserve those on all browsers */
213 code = code.replace(/(\r\n|\n|\r)/gm, "<br/>");
213 code = code.replace(/(\r\n|\n|\r)/gm, "<br/>");
214 code = code.replace(/ /gm, '&nbsp;');
214 code = code.replace(/ /gm, '&nbsp;');
215 code = prettyPrintOne(code);
215 code = prettyPrintOne(code);
216
216
217 return '<code class="prettyprint">' + code + '</code>';
217 return '<code class="prettyprint">' + code + '</code>';
218 });
218 });
219 this.rendered = true;
219 this.rendered = true;
220 }
220 }
221 };
221 };
222
222
223
223
224 // RSTCell
224 // RSTCell
225
225
226 var RSTCell = function (notebook) {
226 var RSTCell = function (notebook) {
227 this.placeholder = "\u0000Type *ReStructured Text* and LaTeX: $\\alpha^2$";
227 this.placeholder = "\u0000Type *ReStructured Text* and LaTeX: $\\alpha^2$";
228 IPython.TextCell.apply(this, arguments);
228 IPython.TextCell.apply(this, arguments);
229 this.cell_type = 'rst';
229 this.cell_type = 'rst';
230 };
230 };
231
231
232
232
233 RSTCell.prototype = new TextCell();
233 RSTCell.prototype = new TextCell();
234
234
235
235
236 RSTCell.prototype.render = function () {
236 RSTCell.prototype.render = function () {
237 if (this.rendered === false) {
237 if (this.rendered === false) {
238 var text = this.get_source();
238 var text = this.get_source();
239 if (text === "") { text = this.placeholder; }
239 if (text === "") { text = this.placeholder; }
240 var settings = {
240 var settings = {
241 processData : false,
241 processData : false,
242 cache : false,
242 cache : false,
243 type : "POST",
243 type : "POST",
244 data : text,
244 data : text,
245 headers : {'Content-Type': 'application/x-rst'},
245 headers : {'Content-Type': 'application/x-rst'},
246 success : $.proxy(this.handle_render,this)
246 success : $.proxy(this.handle_render,this)
247 };
247 };
248 $.ajax("/rstservice/render", settings);
248 $.ajax("/rstservice/render", settings);
249 this.element.find('div.text_cell_input').hide();
249 this.element.find('div.text_cell_input').hide();
250 this.element.find("div.text_cell_render").show();
250 this.element.find("div.text_cell_render").show();
251 this.set_rendered("Rendering...");
251 this.set_rendered("Rendering...");
252 }
252 }
253 };
253 };
254
254
255
255
256 RSTCell.prototype.handle_render = function (data, status, xhr) {
256 RSTCell.prototype.handle_render = function (data, status, xhr) {
257 this.set_rendered(data);
257 this.set_rendered(data);
258 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
258 this.typeset();
259 this.rendered = true;
259 this.rendered = true;
260 };
260 };
261
261
262
262
263 IPython.TextCell = TextCell;
263 IPython.TextCell = TextCell;
264 IPython.HTMLCell = HTMLCell;
264 IPython.HTMLCell = HTMLCell;
265 IPython.MarkdownCell = MarkdownCell;
265 IPython.MarkdownCell = MarkdownCell;
266 IPython.RSTCell = RSTCell;
266 IPython.RSTCell = RSTCell;
267
267
268
268
269 return IPython;
269 return IPython;
270
270
271 }(IPython));
271 }(IPython));
272
272
@@ -1,327 +1,303 b''
1 <!DOCTYPE HTML>
1 <!DOCTYPE HTML>
2 <html>
2 <html>
3
3
4 <head>
4 <head>
5 <meta charset="utf-8">
5 <meta charset="utf-8">
6
6
7 <title>IPython Notebook</title>
7 <title>IPython Notebook</title>
8
8
9 {% if enable_mathjax %}
9 <!-- <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" charset="utf-8"></script> -->
10 <!-- <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" charset="utf-8"></script> -->
10 <script type='text/javascript' src='static/mathjax/MathJax.js?config=TeX-AMS_HTML' charset='utf-8'></script>
11 <script type='text/javascript' src='static/mathjax/MathJax.js?config=TeX-AMS_HTML' charset='utf-8'></script>
11 <script type="text/javascript">
12 <script type="text/javascript">
12 function CheckMathJax(){
13 if (typeof(MathJax) == 'undefined') {
13 var div=document.getElementById("MathJaxFetchingWarning")
14 if(window.MathJax){
15 document.body.removeChild(div)
16 }
17 else{
18 div.style.display = "block";
19 }
20 }
21 if (typeof MathJax == 'undefined') {
22 console.log("No local MathJax, loading from CDN");
14 console.log("No local MathJax, loading from CDN");
23 document.write(unescape("%3Cscript type='text/javascript' src='http://cdn.mathjax.org/mathjax/latest/MathJax.js%3Fconfig=TeX-AMS_HTML' charset='utf-8'%3E%3C/script%3E"));
15 document.write(unescape("%3Cscript type='text/javascript' src='http://cdn.mathjax.org/mathjax/latest/MathJax.js%3Fconfig=TeX-AMS_HTML' charset='utf-8'%3E%3C/script%3E"));
24 }else{
16 }else{
25 console.log("Using local MathJax");
17 console.log("Using local MathJax");
26 }
18 }
27 </script>
19 </script>
20 {% else %}
21 <script type="text/javascript">
22 // MathJax disabled, set as null to distingish from *missing* MathJax,
23 // where it will be undefined, and should prompt a dialog later.
24 window.MathJax = null;
25 </script>
26 {% end %}
28
27
29 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
28 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
30 <link rel="stylesheet" href="static/codemirror/lib/codemirror.css">
29 <link rel="stylesheet" href="static/codemirror/lib/codemirror.css">
31 <link rel="stylesheet" href="static/codemirror/mode/markdown/markdown.css">
30 <link rel="stylesheet" href="static/codemirror/mode/markdown/markdown.css">
32 <link rel="stylesheet" href="static/codemirror/mode/rst/rst.css">
31 <link rel="stylesheet" href="static/codemirror/mode/rst/rst.css">
33 <link rel="stylesheet" href="static/codemirror/theme/ipython.css">
32 <link rel="stylesheet" href="static/codemirror/theme/ipython.css">
34 <link rel="stylesheet" href="static/codemirror/theme/default.css">
33 <link rel="stylesheet" href="static/codemirror/theme/default.css">
35
34
36 <link rel="stylesheet" href="static/prettify/prettify.css"/>
35 <link rel="stylesheet" href="static/prettify/prettify.css"/>
37
36
38 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
37 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
39 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
38 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
40 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
39 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
41 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
40 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
42 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
41 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
43
42
44 <meta name="read_only" content="{{read_only}}"/>
43 <meta name="read_only" content="{{read_only}}"/>
45
44
46 </head>
45 </head>
47
46
48 <body onload='CheckMathJax();'
47 <body
49 data-project={{project}} data-notebook-id={{notebook_id}}
48 data-project={{project}} data-notebook-id={{notebook_id}}
50 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
49 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
51 >
50 >
52
51
53 <div id="header">
52 <div id="header">
54 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
53 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
55 <span id="save_widget">
54 <span id="save_widget">
56 <input type="text" id="notebook_name" size="20"></textarea>
55 <input type="text" id="notebook_name" size="20"></textarea>
57 <button id="save_notebook"><u>S</u>ave</button>
56 <button id="save_notebook"><u>S</u>ave</button>
58 </span>
57 </span>
59 <span id="quick_help_area">
58 <span id="quick_help_area">
60 <button id="quick_help">Quick<u>H</u>elp</button>
59 <button id="quick_help">Quick<u>H</u>elp</button>
61 </span>
60 </span>
62
61
63 <span id="login_widget">
62 <span id="login_widget">
64 {% comment This is a temporary workaround to hide the logout button %}
63 {% comment This is a temporary workaround to hide the logout button %}
65 {% comment when appropriate until notebook.html is templated %}
64 {% comment when appropriate until notebook.html is templated %}
66 {% if current_user and current_user != 'anonymous' %}
65 {% if current_user and current_user != 'anonymous' %}
67 <button id="logout">Logout</button>
66 <button id="logout">Logout</button>
68 {% end %}
67 {% end %}
69 </span>
68 </span>
70
69
71 <span id="kernel_status">Idle</span>
70 <span id="kernel_status">Idle</span>
72 </div>
71 </div>
73
72
74 <div id="MathJaxFetchingWarning"
75 style="width:80%; margin:auto;padding-top:20%;text-align: justify; display:none">
76 <p style="font-size:26px;">There was an issue trying to fetch MathJax.js
77 from the internet.</p>
78
79 <p style="padding:0.2em"> With a working internet connection, you can run
80 the following at a Python or IPython prompt, which will install a local
81 copy of MathJax:</p>
82
83 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
84 from IPython.external import mathjax; mathjax.install_mathjax()
85 </pre>
86 This will try to install MathJax into the directory where you installed
87 IPython. If you installed IPython to a location that requires
88 administrative privileges to write, you will need to make this call as
89 an administrator. On OSX/Linux/Unix, this can be done at the
90 command-line via:
91 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
92 sudo python -c "from IPython.external import mathjax; mathjax.install_mathjax()"
93 </pre>
94 </p>
95 </div>
96
97 <div id="main_app">
73 <div id="main_app">
98
74
99 <div id="left_panel">
75 <div id="left_panel">
100
76
101 <div id="notebook_section">
77 <div id="notebook_section">
102 <div class="section_header">
78 <div class="section_header">
103 <h3>Notebook</h3>
79 <h3>Notebook</h3>
104 </div>
80 </div>
105 <div class="section_content">
81 <div class="section_content">
106 <div class="section_row">
82 <div class="section_row">
107 <span id="new_open" class="section_row_buttons">
83 <span id="new_open" class="section_row_buttons">
108 <button id="new_notebook">New</button>
84 <button id="new_notebook">New</button>
109 <button id="open_notebook">Open</button>
85 <button id="open_notebook">Open</button>
110 </span>
86 </span>
111 <span class="section_row_header">Actions</span>
87 <span class="section_row_header">Actions</span>
112 </div>
88 </div>
113 <div class="section_row">
89 <div class="section_row">
114 <span>
90 <span>
115 <select id="download_format">
91 <select id="download_format">
116 <option value="json">ipynb</option>
92 <option value="json">ipynb</option>
117 <option value="py">py</option>
93 <option value="py">py</option>
118 </select>
94 </select>
119 </span>
95 </span>
120 <span class="section_row_buttons">
96 <span class="section_row_buttons">
121 <button id="download_notebook">Download</button>
97 <button id="download_notebook">Download</button>
122 </span>
98 </span>
123 </div>
99 </div>
124 <div class="section_row">
100 <div class="section_row">
125 <span class="section_row_buttons">
101 <span class="section_row_buttons">
126 <span id="print_widget">
102 <span id="print_widget">
127 <button id="print_notebook">Print</button>
103 <button id="print_notebook">Print</button>
128 </span>
104 </span>
129 </span>
105 </span>
130 </div>
106 </div>
131 </div>
107 </div>
132 </div>
108 </div>
133
109
134 <div id="cell_section">
110 <div id="cell_section">
135 <div class="section_header">
111 <div class="section_header">
136 <h3>Cell</h3>
112 <h3>Cell</h3>
137 </div>
113 </div>
138 <div class="section_content">
114 <div class="section_content">
139 <div class="section_row">
115 <div class="section_row">
140 <span class="section_row_buttons">
116 <span class="section_row_buttons">
141 <button id="delete_cell"><u>D</u>elete</button>
117 <button id="delete_cell"><u>D</u>elete</button>
142 </span>
118 </span>
143 <span class="section_row_header">Actions</span>
119 <span class="section_row_header">Actions</span>
144 </div>
120 </div>
145 <div class="section_row">
121 <div class="section_row">
146 <span id="cell_type" class="section_row_buttons">
122 <span id="cell_type" class="section_row_buttons">
147 <button id="to_code"><u>C</u>ode</button>
123 <button id="to_code"><u>C</u>ode</button>
148 <!-- <button id="to_html">HTML</button>-->
124 <!-- <button id="to_html">HTML</button>-->
149 <button id="to_markdown"><u>M</u>arkdown</button>
125 <button id="to_markdown"><u>M</u>arkdown</button>
150 </span>
126 </span>
151 <span class="button_label">Format</span>
127 <span class="button_label">Format</span>
152 </div>
128 </div>
153 <div class="section_row">
129 <div class="section_row">
154 <span id="cell_output" class="section_row_buttons">
130 <span id="cell_output" class="section_row_buttons">
155 <button id="toggle_output"><u>T</u>oggle</button>
131 <button id="toggle_output"><u>T</u>oggle</button>
156 <button id="clear_all_output">ClearAll</button>
132 <button id="clear_all_output">ClearAll</button>
157 </span>
133 </span>
158 <span class="button_label">Output</span>
134 <span class="button_label">Output</span>
159 </div>
135 </div>
160 <div class="section_row">
136 <div class="section_row">
161 <span id="insert" class="section_row_buttons">
137 <span id="insert" class="section_row_buttons">
162 <button id="insert_cell_above"><u>A</u>bove</button>
138 <button id="insert_cell_above"><u>A</u>bove</button>
163 <button id="insert_cell_below"><u>B</u>elow</button>
139 <button id="insert_cell_below"><u>B</u>elow</button>
164 </span>
140 </span>
165 <span class="button_label">Insert</span>
141 <span class="button_label">Insert</span>
166 </div>
142 </div>
167 <div class="section_row">
143 <div class="section_row">
168 <span id="move" class="section_row_buttons">
144 <span id="move" class="section_row_buttons">
169 <button id="move_cell_up">Up</button>
145 <button id="move_cell_up">Up</button>
170 <button id="move_cell_down">Down</button>
146 <button id="move_cell_down">Down</button>
171 </span>
147 </span>
172 <span class="button_label">Move</span>
148 <span class="button_label">Move</span>
173 </div>
149 </div>
174 <div class="section_row">
150 <div class="section_row">
175 <span id="run_cells" class="section_row_buttons">
151 <span id="run_cells" class="section_row_buttons">
176 <button id="run_selected_cell">Selected</button>
152 <button id="run_selected_cell">Selected</button>
177 <button id="run_all_cells">All</button>
153 <button id="run_all_cells">All</button>
178 </span>
154 </span>
179 <span class="button_label">Run</span>
155 <span class="button_label">Run</span>
180 </div>
156 </div>
181 <div class="section_row">
157 <div class="section_row">
182 <span id="autoindent_span">
158 <span id="autoindent_span">
183 <input type="checkbox" id="autoindent" checked="true"></input>
159 <input type="checkbox" id="autoindent" checked="true"></input>
184 </span>
160 </span>
185 <span class="checkbox_label" id="autoindent_label">Autoindent:</span>
161 <span class="checkbox_label" id="autoindent_label">Autoindent:</span>
186 </div>
162 </div>
187 </div>
163 </div>
188 </div>
164 </div>
189
165
190 <div id="kernel_section">
166 <div id="kernel_section">
191 <div class="section_header">
167 <div class="section_header">
192 <h3>Kernel</h3>
168 <h3>Kernel</h3>
193 </div>
169 </div>
194 <div class="section_content">
170 <div class="section_content">
195 <div class="section_row">
171 <div class="section_row">
196 <span id="int_restart" class="section_row_buttons">
172 <span id="int_restart" class="section_row_buttons">
197 <button id="int_kernel"><u>I</u>nterrupt</button>
173 <button id="int_kernel"><u>I</u>nterrupt</button>
198 <button id="restart_kernel">Restart</button>
174 <button id="restart_kernel">Restart</button>
199 </span>
175 </span>
200 <span class="section_row_header">Actions</span>
176 <span class="section_row_header">Actions</span>
201 </div>
177 </div>
202 <div class="section_row">
178 <div class="section_row">
203 <span id="kernel_persist">
179 <span id="kernel_persist">
204 {% if kill_kernel %}
180 {% if kill_kernel %}
205 <input type="checkbox" id="kill_kernel" checked="true"></input>
181 <input type="checkbox" id="kill_kernel" checked="true"></input>
206 {% else %}
182 {% else %}
207 <input type="checkbox" id="kill_kernel"></input>
183 <input type="checkbox" id="kill_kernel"></input>
208 {% end %}
184 {% end %}
209 </span>
185 </span>
210 <span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
186 <span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
211 </div>
187 </div>
212 </div>
188 </div>
213 </div>
189 </div>
214
190
215 <div id="help_section">
191 <div id="help_section">
216 <div class="section_header">
192 <div class="section_header">
217 <h3>Help</h3>
193 <h3>Help</h3>
218 </div>
194 </div>
219 <div class="section_content">
195 <div class="section_content">
220 <div class="section_row">
196 <div class="section_row">
221 <span id="help_buttons0" class="section_row_buttons">
197 <span id="help_buttons0" class="section_row_buttons">
222 <a id="python_help" href="http://docs.python.org" target="_blank">Python</a>
198 <a id="python_help" href="http://docs.python.org" target="_blank">Python</a>
223 <a id="ipython_help" href="http://ipython.org/documentation.html" target="_blank">IPython</a>
199 <a id="ipython_help" href="http://ipython.org/documentation.html" target="_blank">IPython</a>
224 </span>
200 </span>
225 <span class="section_row_header">Links</span>
201 <span class="section_row_header">Links</span>
226 </div>
202 </div>
227 <div class="section_row">
203 <div class="section_row">
228 <span id="help_buttons1" class="section_row_buttons">
204 <span id="help_buttons1" class="section_row_buttons">
229 <a id="numpy_help" href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a>
205 <a id="numpy_help" href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a>
230 <a id="scipy_help" href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a>
206 <a id="scipy_help" href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a>
231 </span>
207 </span>
232 </div>
208 </div>
233 <div class="section_row">
209 <div class="section_row">
234 <span id="help_buttons2" class="section_row_buttons">
210 <span id="help_buttons2" class="section_row_buttons">
235 <a id="matplotlib_help" href="http://matplotlib.sourceforge.net/" target="_blank">MPL</a>
211 <a id="matplotlib_help" href="http://matplotlib.sourceforge.net/" target="_blank">MPL</a>
236 <a id="sympy_help" href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a>
212 <a id="sympy_help" href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a>
237 </span>
213 </span>
238 </div>
214 </div>
239 <div class="section_row">
215 <div class="section_row">
240 <span class="help_string">run selected cell</span>
216 <span class="help_string">run selected cell</span>
241 <span class="help_string_label">Shift-Enter :</span>
217 <span class="help_string_label">Shift-Enter :</span>
242 </div>
218 </div>
243 <div class="section_row">
219 <div class="section_row">
244 <span class="help_string">run selected cell in-place</span>
220 <span class="help_string">run selected cell in-place</span>
245 <span class="help_string_label">Ctrl-Enter :</span>
221 <span class="help_string_label">Ctrl-Enter :</span>
246 </div>
222 </div>
247 <div class="section_row">
223 <div class="section_row">
248 <span class="help_string">show keyboard shortcuts</span>
224 <span class="help_string">show keyboard shortcuts</span>
249 <span class="help_string_label">Ctrl-m h :</span>
225 <span class="help_string_label">Ctrl-m h :</span>
250 </div>
226 </div>
251 </div>
227 </div>
252 </div>
228 </div>
253
229
254 <div id="config_section">
230 <div id="config_section">
255 <div class="section_header">
231 <div class="section_header">
256 <h3>Config</h3>
232 <h3>Config</h3>
257 </div>
233 </div>
258 <div class="section_content">
234 <div class="section_content">
259 <div class="section_row">
235 <div class="section_row">
260 <span id="tooltipontab_span">
236 <span id="tooltipontab_span">
261 <input type="checkbox" id="tooltipontab" checked="true"></input>
237 <input type="checkbox" id="tooltipontab" checked="true"></input>
262 </span>
238 </span>
263 <span class="checkbox_label" id="tooltipontab_label">Tooltip on tab:</span>
239 <span class="checkbox_label" id="tooltipontab_label">Tooltip on tab:</span>
264 </div>
240 </div>
265 <div class="section_row">
241 <div class="section_row">
266 <span id="smartcompleter_span">
242 <span id="smartcompleter_span">
267 <input type="checkbox" id="smartcompleter" checked="true"></input>
243 <input type="checkbox" id="smartcompleter" checked="true"></input>
268 </span>
244 </span>
269 <span class="checkbox_label" id="smartcompleter_label">Smart completer:</span>
245 <span class="checkbox_label" id="smartcompleter_label">Smart completer:</span>
270 </div>
246 </div>
271 <div class="section_row">
247 <div class="section_row">
272 <span id="timebeforetooltip_span">
248 <span id="timebeforetooltip_span">
273 <input type="text" id="timebeforetooltip" value="1200"></input>
249 <input type="text" id="timebeforetooltip" value="1200"></input>
274 </span>
250 </span>
275 <span class="numeric_input_label" id="timebeforetooltip_label">Time before tooltip : </span>
251 <span class="numeric_input_label" id="timebeforetooltip_label">Time before tooltip : </span>
276 </div>
252 </div>
277 </div>
253 </div>
278 </div>
254 </div>
279
255
280 </div>
256 </div>
281 <div id="left_panel_splitter"></div>
257 <div id="left_panel_splitter"></div>
282 <div id="notebook_panel">
258 <div id="notebook_panel">
283 <div id="notebook"></div>
259 <div id="notebook"></div>
284 <div id="pager_splitter"></div>
260 <div id="pager_splitter"></div>
285 <div id="pager"></div>
261 <div id="pager"></div>
286 </div>
262 </div>
287
263
288 </div>
264 </div>
289
265
290 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
266 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
291 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
267 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
292 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
268 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
293
269
294 <script src="static/codemirror/lib/codemirror.js" charset="utf-8"></script>
270 <script src="static/codemirror/lib/codemirror.js" charset="utf-8"></script>
295 <script src="static/codemirror/mode/python/python.js" charset="utf-8"></script>
271 <script src="static/codemirror/mode/python/python.js" charset="utf-8"></script>
296 <script src="static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
272 <script src="static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
297 <script src="static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
273 <script src="static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
298 <script src="static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
274 <script src="static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
299 <script src="static/codemirror/mode/css/css.js" charset="utf-8"></script>
275 <script src="static/codemirror/mode/css/css.js" charset="utf-8"></script>
300 <script src="static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
276 <script src="static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
301 <script src="static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
277 <script src="static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
302
278
303 <script src="static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
279 <script src="static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
304
280
305 <script src="static/prettify/prettify.js" charset="utf-8"></script>
281 <script src="static/prettify/prettify.js" charset="utf-8"></script>
306
282
307 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
283 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
308 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
284 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
309 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
285 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
310 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
286 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
311 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
287 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
312 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
288 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
313 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
289 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
314 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
290 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
315 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
291 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
316 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
292 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
317 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
293 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
318 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
294 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
319 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
295 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
320 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
296 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
321 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
297 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
322 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
298 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
323 <script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
299 <script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
324
300
325 </body>
301 </body>
326
302
327 </html>
303 </html>
General Comments 0
You need to be logged in to leave comments. Login now