##// END OF EJS Templates
Merge pull request #1077 from minrk/nb...
Min RK -
r5575:00404468 merge
parent child Browse files
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 mathjax_url=self.application.ipython_app.mathjax_url,
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 mathjax_url=self.application.ipython_app.mathjax_url,
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,385 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 )
233
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 )
255 def _enable_mathjax_changed(self, name, old, new):
256 """set mathjax url to empty if mathjax is disabled"""
257 if not new:
258 self.mathjax_url = u''
259
260 mathjax_url = Unicode("", config=True,
261 help="""The url for MathJax.js."""
262 )
263 def _mathjax_url_default(self):
264 if not self.enable_mathjax:
265 return u''
266 static_path = os.path.join(os.path.dirname(__file__), "static")
267 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
268 self.log.info("Using local MathJax")
269 return u"static/mathjax/MathJax.js"
270 else:
271 self.log.info("Using MathJax from CDN")
272 return u"http://cdn.mathjax.org/mathjax/latest/MathJax.js"
273
274 def _mathjax_url_changed(self, name, old, new):
275 if new and not self.enable_mathjax:
276 # enable_mathjax=False overrides mathjax_url
277 self.mathjax_url = u''
278 else:
279 self.log.info("Using MathJax: %s", new)
280
234 def parse_command_line(self, argv=None):
281 def parse_command_line(self, argv=None):
235 super(NotebookApp, self).parse_command_line(argv)
282 super(NotebookApp, self).parse_command_line(argv)
236 if argv is None:
283 if argv is None:
237 argv = sys.argv[1:]
284 argv = sys.argv[1:]
238
285
239 self.kernel_argv = list(argv) # copy
286 self.kernel_argv = list(argv) # copy
240 # Kernel should inherit default config file from frontend
287 # Kernel should inherit default config file from frontend
241 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
288 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
242 # Scrub frontend-specific flags
289 # Scrub frontend-specific flags
243 for a in argv:
290 for a in argv:
244 if a.startswith('-') and a.lstrip('-') in notebook_flags:
291 if a.startswith('-') and a.lstrip('-') in notebook_flags:
245 self.kernel_argv.remove(a)
292 self.kernel_argv.remove(a)
246 swallow_next = False
293 swallow_next = False
247 for a in argv:
294 for a in argv:
248 if swallow_next:
295 if swallow_next:
249 self.kernel_argv.remove(a)
296 self.kernel_argv.remove(a)
250 swallow_next = False
297 swallow_next = False
251 continue
298 continue
252 if a.startswith('-'):
299 if a.startswith('-'):
253 split = a.lstrip('-').split('=')
300 split = a.lstrip('-').split('=')
254 alias = split[0]
301 alias = split[0]
255 if alias in notebook_aliases:
302 if alias in notebook_aliases:
256 self.kernel_argv.remove(a)
303 self.kernel_argv.remove(a)
257 if len(split) == 1:
304 if len(split) == 1:
258 # alias passed with arg via space
305 # alias passed with arg via space
259 swallow_next = True
306 swallow_next = True
260
307
261 def init_configurables(self):
308 def init_configurables(self):
262 # Don't let Qt or ZMQ swallow KeyboardInterupts.
309 # Don't let Qt or ZMQ swallow KeyboardInterupts.
263 signal.signal(signal.SIGINT, signal.SIG_DFL)
310 signal.signal(signal.SIGINT, signal.SIG_DFL)
264
311
265 # force Session default to be secure
312 # force Session default to be secure
266 default_secure(self.config)
313 default_secure(self.config)
267 # Create a KernelManager and start a kernel.
314 # Create a KernelManager and start a kernel.
268 self.kernel_manager = MappingKernelManager(
315 self.kernel_manager = MappingKernelManager(
269 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
316 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
270 connection_dir = self.profile_dir.security_dir,
317 connection_dir = self.profile_dir.security_dir,
271 )
318 )
272 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
319 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
273 self.notebook_manager.list_notebooks()
320 self.notebook_manager.list_notebooks()
274
321
275 def init_logging(self):
322 def init_logging(self):
276 super(NotebookApp, self).init_logging()
323 super(NotebookApp, self).init_logging()
277 # This prevents double log messages because tornado use a root logger that
324 # 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
325 # 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.
326 # and all of its ancenstors until propagate is set to False.
280 self.log.propagate = False
327 self.log.propagate = False
281
328
282 @catch_config_error
329 @catch_config_error
283 def initialize(self, argv=None):
330 def initialize(self, argv=None):
284 super(NotebookApp, self).initialize(argv)
331 super(NotebookApp, self).initialize(argv)
285 self.init_configurables()
332 self.init_configurables()
286 self.web_app = NotebookWebApplication(
333 self.web_app = NotebookWebApplication(
287 self, self.kernel_manager, self.notebook_manager, self.log
334 self, self.kernel_manager, self.notebook_manager, self.log
288 )
335 )
289 if self.certfile:
336 if self.certfile:
290 ssl_options = dict(certfile=self.certfile)
337 ssl_options = dict(certfile=self.certfile)
291 if self.keyfile:
338 if self.keyfile:
292 ssl_options['keyfile'] = self.keyfile
339 ssl_options['keyfile'] = self.keyfile
293 else:
340 else:
294 ssl_options = None
341 ssl_options = None
295 self.web_app.password = self.password
342 self.web_app.password = self.password
296 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
343 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
297 if ssl_options is None and not self.ip:
344 if ssl_options is None and not self.ip:
298 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
345 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
299 'but not using any encryption or authentication. This is highly '
346 'but not using any encryption or authentication. This is highly '
300 'insecure and not recommended.')
347 'insecure and not recommended.')
301
348
302 # Try random ports centered around the default.
349 # Try random ports centered around the default.
303 from random import randint
350 from random import randint
304 n = 50 # Max number of attempts, keep reasonably large.
351 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)]:
352 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
306 try:
353 try:
307 self.http_server.listen(port, self.ip)
354 self.http_server.listen(port, self.ip)
308 except socket.error, e:
355 except socket.error, e:
309 if e.errno != errno.EADDRINUSE:
356 if e.errno != errno.EADDRINUSE:
310 raise
357 raise
311 self.log.info('The port %i is already in use, trying another random port.' % port)
358 self.log.info('The port %i is already in use, trying another random port.' % port)
312 else:
359 else:
313 self.port = port
360 self.port = port
314 break
361 break
315
362
316 def start(self):
363 def start(self):
317 ip = self.ip if self.ip else '[all ip addresses on your system]'
364 ip = self.ip if self.ip else '[all ip addresses on your system]'
318 proto = 'https' if self.certfile else 'http'
365 proto = 'https' if self.certfile else 'http'
319 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
366 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
320 ip,
367 ip,
321 self.port))
368 self.port))
322 if self.open_browser:
369 if self.open_browser:
323 ip = self.ip or '127.0.0.1'
370 ip = self.ip or '127.0.0.1'
324 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
371 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
325 new=2)
372 new=2)
326 threading.Thread(target=b).start()
373 threading.Thread(target=b).start()
327
374
328 ioloop.IOLoop.instance().start()
375 ioloop.IOLoop.instance().start()
329
376
330 #-----------------------------------------------------------------------------
377 #-----------------------------------------------------------------------------
331 # Main entry point
378 # Main entry point
332 #-----------------------------------------------------------------------------
379 #-----------------------------------------------------------------------------
333
380
334 def launch_new_instance():
381 def launch_new_instance():
335 app = NotebookApp()
382 app = NotebookApp()
336 app.initialize()
383 app.initialize()
337 app.start()
384 app.start()
338
385
@@ -1,449 +1,461 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{
118 #timebeforetooltip{
119 margin-top:-3px;
119 margin-top:-3px;
120 text-align:right;
120 text-align:right;
121 }
121 }
122
122
123 #timebeforetooltip_span {
123 #timebeforetooltip_span {
124 float: right;
124 float: right;
125 padding: 0px 5px;
125 padding: 0px 5px;
126 font-size: 85%;
126 font-size: 85%;
127 }
127 }
128
128
129 #timebeforetooltip_label {
129 #timebeforetooltip_label {
130 float: right;
130 float: right;
131 text-align:right;
131 text-align:right;
132 font-size: 85%;
132 font-size: 85%;
133 }
133 }
134
134
135 #tooltipontab_span {
135 #tooltipontab_span {
136 float: right;
136 float: right;
137 }
137 }
138
138
139 #smartcompleter_span {
139 #smartcompleter_span {
140 float: right;
140 float: right;
141 }
141 }
142
142
143 .checkbox_label {
143 .checkbox_label {
144 font-size: 85%;
144 font-size: 85%;
145 float: right;
145 float: right;
146 padding: 0.3em;
146 padding: 0.3em;
147 }
147 }
148
148
149 .section_row_header {
149 .section_row_header {
150 float: left;
150 float: left;
151 font-size: 85%;
151 font-size: 85%;
152 padding: 0.4em 0em;
152 padding: 0.4em 0em;
153 font-weight: bold;
153 font-weight: bold;
154 }
154 }
155
155
156 span.button_label {
156 span.button_label {
157 padding: 0.2em 1em;
157 padding: 0.2em 1em;
158 font-size: 77%;
158 font-size: 77%;
159 float: right;
159 float: right;
160 }
160 }
161
161
162 /* This is needed because FF was adding a 2px margin top and bottom. */
162 /* This is needed because FF was adding a 2px margin top and bottom. */
163 .section_row .ui-button {
163 .section_row .ui-button {
164 margin-top: 0px;
164 margin-top: 0px;
165 margin-bottom: 0px;
165 margin-bottom: 0px;
166 }
166 }
167
167
168 #download_format {
168 #download_format {
169 float: right;
169 float: right;
170 font-size: 85%;
170 font-size: 85%;
171 width: 62px;
171 width: 62px;
172 margin: 1px 5px;
172 margin: 1px 5px;
173 }
173 }
174
174
175 div#left_panel_splitter {
175 div#left_panel_splitter {
176 width: 8px;
176 width: 8px;
177 top: 0px;
177 top: 0px;
178 left: 202px;
178 left: 202px;
179 margin: 0px;
179 margin: 0px;
180 padding: 0px;
180 padding: 0px;
181 position: absolute;
181 position: absolute;
182 }
182 }
183
183
184 div#notebook_panel {
184 div#notebook_panel {
185 /* The L margin will be set in the Javascript code*/
185 /* The L margin will be set in the Javascript code*/
186 margin: 0px 0px 0px 0px;
186 margin: 0px 0px 0px 0px;
187 padding: 0px;
187 padding: 0px;
188 }
188 }
189
189
190 div#notebook {
190 div#notebook {
191 overflow-y: scroll;
191 overflow-y: scroll;
192 overflow-x: auto;
192 overflow-x: auto;
193 width: 100%;
193 width: 100%;
194 /* This spaces the cell away from the edge of the notebook area */
194 /* This spaces the cell away from the edge of the notebook area */
195 padding: 5px 5px 15px 5px;
195 padding: 5px 5px 15px 5px;
196 margin: 0px
196 margin: 0px
197 background-color: white;
197 background-color: white;
198 }
198 }
199
199
200 div#pager_splitter {
200 div#pager_splitter {
201 height: 8px;
201 height: 8px;
202 }
202 }
203
203
204 div#pager {
204 div#pager {
205 padding: 15px;
205 padding: 15px;
206 overflow: auto;
206 overflow: auto;
207 }
207 }
208
208
209 div.cell {
209 div.cell {
210 width: 100%;
210 width: 100%;
211 padding: 5px 5px 5px 0px;
211 padding: 5px 5px 5px 0px;
212 /* This acts as a spacer between cells, that is outside the border */
212 /* This acts as a spacer between cells, that is outside the border */
213 margin: 2px 0px 2px 0px;
213 margin: 2px 0px 2px 0px;
214 }
214 }
215
215
216 div.code_cell {
216 div.code_cell {
217 background-color: white;
217 background-color: white;
218 }
218 }
219 /* any special styling for code cells that are currently running goes here */
219 /* any special styling for code cells that are currently running goes here */
220 div.code_cell.running {
220 div.code_cell.running {
221 }
221 }
222
222
223 div.prompt {
223 div.prompt {
224 /* This needs to be wide enough for 3 digit prompt numbers: In[100]: */
224 /* This needs to be wide enough for 3 digit prompt numbers: In[100]: */
225 width: 11ex;
225 width: 11ex;
226 /* This 0.4em is tuned to match the padding on the CodeMirror editor. */
226 /* This 0.4em is tuned to match the padding on the CodeMirror editor. */
227 padding: 0.4em;
227 padding: 0.4em;
228 margin: 0px;
228 margin: 0px;
229 font-family: monospace;
229 font-family: monospace;
230 text-align:right;
230 text-align:right;
231 }
231 }
232
232
233 div.input {
233 div.input {
234 page-break-inside: avoid;
234 page-break-inside: avoid;
235 }
235 }
236
236
237 /* input_area and input_prompt must match in top border and margin for alignment */
237 /* input_area and input_prompt must match in top border and margin for alignment */
238 div.input_area {
238 div.input_area {
239 color: black;
239 color: black;
240 border: 1px solid #ddd;
240 border: 1px solid #ddd;
241 border-radius: 3px;
241 border-radius: 3px;
242 background: #f7f7f7;
242 background: #f7f7f7;
243 }
243 }
244
244
245 div.input_prompt {
245 div.input_prompt {
246 color: navy;
246 color: navy;
247 border-top: 1px solid transparent;
247 border-top: 1px solid transparent;
248 }
248 }
249
249
250 div.output {
250 div.output {
251 /* This is a spacer between the input and output of each cell */
251 /* This is a spacer between the input and output of each cell */
252 margin-top: 5px;
252 margin-top: 5px;
253 }
253 }
254
254
255 div.output_prompt {
255 div.output_prompt {
256 color: darkred;
256 color: darkred;
257 }
257 }
258
258
259 /* This class is the outer container of all output sections. */
259 /* This class is the outer container of all output sections. */
260 div.output_area {
260 div.output_area {
261 padding: 0px;
261 padding: 0px;
262 page-break-inside: avoid;
262 page-break-inside: avoid;
263 }
263 }
264
264
265 /* This class is for the output subarea inside the output_area and after
265 /* This class is for the output subarea inside the output_area and after
266 the prompt div. */
266 the prompt div. */
267 div.output_subarea {
267 div.output_subarea {
268 padding: 0.4em 6.1em 0.4em 0.4em;
268 padding: 0.4em 6.1em 0.4em 0.4em;
269 }
269 }
270
270
271 /* The rest of the output_* classes are for special styling of the different
271 /* The rest of the output_* classes are for special styling of the different
272 output types */
272 output types */
273
273
274 /* all text output has this class: */
274 /* all text output has this class: */
275 div.output_text {
275 div.output_text {
276 text-align: left;
276 text-align: left;
277 color: black;
277 color: black;
278 font-family: monospace;
278 font-family: monospace;
279 }
279 }
280
280
281 /* stdout/stderr are 'text' as well as 'stream', but pyout/pyerr are *not* streams */
281 /* stdout/stderr are 'text' as well as 'stream', but pyout/pyerr are *not* streams */
282 div.output_stream {
282 div.output_stream {
283 padding-top: 0.0em;
283 padding-top: 0.0em;
284 padding-bottom: 0.0em;
284 padding-bottom: 0.0em;
285 }
285 }
286 div.output_stdout {
286 div.output_stdout {
287 }
287 }
288 div.output_stderr {
288 div.output_stderr {
289 background: #fdd; /* very light red background for stderr */
289 background: #fdd; /* very light red background for stderr */
290 }
290 }
291
291
292 div.output_latex {
292 div.output_latex {
293 text-align: left;
293 text-align: left;
294 color: black;
294 color: black;
295 }
295 }
296
296
297 div.output_html {
297 div.output_html {
298 }
298 }
299
299
300 div.output_png {
300 div.output_png {
301 }
301 }
302
302
303 div.output_jpeg {
303 div.output_jpeg {
304 }
304 }
305
305
306 div.text_cell {
306 div.text_cell {
307 background-color: white;
307 background-color: white;
308 }
308 }
309
309
310 div.text_cell_input {
310 div.text_cell_input {
311 color: black;
311 color: black;
312 }
312 }
313
313
314 div.text_cell_render {
314 div.text_cell_render {
315 font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
315 font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
316 outline: none;
316 outline: none;
317 resize: none;
317 resize: none;
318 width: inherit;
318 width: inherit;
319 border-style: none;
319 border-style: none;
320 padding: 5px;
320 padding: 5px;
321 color: black;
321 color: black;
322 }
322 }
323
323
324 .CodeMirror {
324 .CodeMirror {
325 line-height: 1.231; /* Changed from 1em to our global default */
325 line-height: 1.231; /* Changed from 1em to our global default */
326 }
326 }
327
327
328 .CodeMirror-scroll {
328 .CodeMirror-scroll {
329 height: auto; /* Changed to auto to autogrow */
329 height: auto; /* Changed to auto to autogrow */
330 /* The CodeMirror docs are a bit fuzzy on if overflow-y should be hidden or visible.*/
330 /* The CodeMirror docs are a bit fuzzy on if overflow-y should be hidden or visible.*/
331 /* We have found that if it is visible, vertical scrollbars appear with font size changes.*/
331 /* We have found that if it is visible, vertical scrollbars appear with font size changes.*/
332 overflow-y: hidden;
332 overflow-y: hidden;
333 overflow-x: auto; /* Changed from auto to remove scrollbar */
333 overflow-x: auto; /* Changed from auto to remove scrollbar */
334 }
334 }
335
335
336 /* CSS font colors for translated ANSI colors. */
336 /* CSS font colors for translated ANSI colors. */
337
337
338
338
339 .ansiblack {color: black;}
339 .ansiblack {color: black;}
340 .ansired {color: darkred;}
340 .ansired {color: darkred;}
341 .ansigreen {color: darkgreen;}
341 .ansigreen {color: darkgreen;}
342 .ansiyellow {color: brown;}
342 .ansiyellow {color: brown;}
343 .ansiblue {color: darkblue;}
343 .ansiblue {color: darkblue;}
344 .ansipurple {color: darkviolet;}
344 .ansipurple {color: darkviolet;}
345 .ansicyan {color: steelblue;}
345 .ansicyan {color: steelblue;}
346 .ansigrey {color: grey;}
346 .ansigrey {color: grey;}
347 .ansibold {font-weight: bold;}
347 .ansibold {font-weight: bold;}
348
348
349 .completions , .tooltip{
349 .completions , .tooltip{
350 position: absolute;
350 position: absolute;
351 z-index: 10;
351 z-index: 10;
352 overflow: auto;
352 overflow: auto;
353 border: 1px solid black;
353 border: 1px solid black;
354 }
354 }
355
355
356 .completions select {
356 .completions select {
357 background: white;
357 background: white;
358 outline: none;
358 outline: none;
359 border: none;
359 border: none;
360 padding: 0px;
360 padding: 0px;
361 margin: 0px;
361 margin: 0px;
362 font-family: monospace;
362 font-family: monospace;
363 }
363 }
364
364
365 @-moz-keyframes fadeIn {
365 @-moz-keyframes fadeIn {
366 from {opacity:0;}
366 from {opacity:0;}
367 to {opacity:1;}
367 to {opacity:1;}
368 }
368 }
369
369
370 @-webkit-keyframes fadeIn {
370 @-webkit-keyframes fadeIn {
371 from {opacity:0;}
371 from {opacity:0;}
372 to {opacity:1;}
372 to {opacity:1;}
373 }
373 }
374
374
375 @keyframes fadeIn {
375 @keyframes fadeIn {
376 from {opacity:0;}
376 from {opacity:0;}
377 to {opacity:1;}
377 to {opacity:1;}
378 }
378 }
379
379
380 /*"close" "expand" and "Open in pager button" of
380 /*"close" "expand" and "Open in pager button" of
381 /* the tooltip*/
381 /* the tooltip*/
382 .tooltip a{
382 .tooltip a{
383 float:right;
383 float:right;
384 }
384 }
385
385
386 /*properties of tooltip after "expand"*/
386 /*properties of tooltip after "expand"*/
387 .bigtooltip{
387 .bigtooltip{
388 height:30%;
388 height:30%;
389 }
389 }
390
390
391 /*properties of tooltip before "expand"*/
391 /*properties of tooltip before "expand"*/
392 .smalltooltip{
392 .smalltooltip{
393 text-overflow: ellipsis;
393 text-overflow: ellipsis;
394 overflow: hidden;
394 overflow: hidden;
395 height:15%;
395 height:15%;
396 }
396 }
397
397
398 .tooltip{
398 .tooltip{
399 /*transition when "expand"ing tooltip */
399 /*transition when "expand"ing tooltip */
400 -webkit-transition-property: height;
400 -webkit-transition-property: height;
401 -webkit-transition-duration: 1s;
401 -webkit-transition-duration: 1s;
402 -moz-transition-property: height;
402 -moz-transition-property: height;
403 -moz-transition-duration: 1s;
403 -moz-transition-duration: 1s;
404 transition-property: height;
404 transition-property: height;
405 transition-duration: 1s;
405 transition-duration: 1s;
406 max-width:700px;
406 max-width:700px;
407 border-radius: 0px 10px 10px 10px;
407 border-radius: 0px 10px 10px 10px;
408 box-shadow: 3px 3px 5px #999;
408 box-shadow: 3px 3px 5px #999;
409 /*fade-in animation when inserted*/
409 /*fade-in animation when inserted*/
410 -webkit-animation: fadeIn 200ms;
410 -webkit-animation: fadeIn 200ms;
411 -moz-animation: fadeIn 200ms;
411 -moz-animation: fadeIn 200ms;
412 animation: fadeIn 200ms;
412 animation: fadeIn 200ms;
413 vertical-align: middle;
413 vertical-align: middle;
414 background: #FDFDD8;
414 background: #FDFDD8;
415 outline: none;
415 outline: none;
416 padding: 3px;
416 padding: 3px;
417 margin: 0px;
417 margin: 0px;
418 font-family: monospace;
418 font-family: monospace;
419 min-height:50px;
419 min-height:50px;
420 }
420 }
421
421
422 .completions p{
422 .completions p{
423 background: #DDF;
423 background: #DDF;
424 /*outline: none;
424 /*outline: none;
425 padding: 0px;*/
425 padding: 0px;*/
426 border-bottom: black solid 1px;
426 border-bottom: black solid 1px;
427 padding: 1px;
427 padding: 1px;
428 font-family: monospace;
428 font-family: monospace;
429 }
429 }
430
430
431 pre.dialog {
432 background-color: #f7f7f7;
433 border: 1px solid #ddd;
434 border-radius: 3px;
435 padding: 0.4em;
436 padding-left: 2em;
437 }
438
439 p.dialog{
440 padding : 0.2em;
441 }
442
431 @media print {
443 @media print {
432 body { overflow: visible !important; }
444 body { overflow: visible !important; }
433 .ui-widget-content { border: 0px; }
445 .ui-widget-content { border: 0px; }
434 }
446 }
435
447
436 .shortcut_key {
448 .shortcut_key {
437 display: inline-block;
449 display: inline-block;
438 width: 13ex;
450 width: 13ex;
439 text-align: right;
451 text-align: right;
440 font-family: monospace;
452 font-family: monospace;
441 }
453 }
442
454
443 .shortcut_descr {
455 .shortcut_descr {
444 }
456 }
445
457
446 /* Word-wrap output correctly. This is the CSS3 spelling, though Firefox seems
458 /* Word-wrap output correctly. This is the CSS3 spelling, though Firefox seems
447 to not honor it correctly. Webkit browsers (Chrome, rekonq, Safari) do.
459 to not honor it correctly. Webkit browsers (Chrome, rekonq, Safari) do.
448 */
460 */
449 pre, code, kbd, samp { white-space: pre-wrap; }
461 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,797 +1,797 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 this.tooltip_timeout = null;
23 this.tooltip_timeout = null;
24 IPython.Cell.apply(this, arguments);
24 IPython.Cell.apply(this, arguments);
25 };
25 };
26
26
27
27
28 CodeCell.prototype = new IPython.Cell();
28 CodeCell.prototype = new IPython.Cell();
29
29
30
30
31 CodeCell.prototype.create_element = function () {
31 CodeCell.prototype.create_element = function () {
32 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
32 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
33 cell.attr('tabindex','2');
33 cell.attr('tabindex','2');
34 var input = $('<div></div>').addClass('input hbox');
34 var input = $('<div></div>').addClass('input hbox');
35 input.append($('<div/>').addClass('prompt input_prompt'));
35 input.append($('<div/>').addClass('prompt input_prompt'));
36 var input_area = $('<div/>').addClass('input_area box-flex1');
36 var input_area = $('<div/>').addClass('input_area box-flex1');
37 this.code_mirror = CodeMirror(input_area.get(0), {
37 this.code_mirror = CodeMirror(input_area.get(0), {
38 indentUnit : 4,
38 indentUnit : 4,
39 mode: 'python',
39 mode: 'python',
40 theme: 'ipython',
40 theme: 'ipython',
41 readOnly: this.read_only,
41 readOnly: this.read_only,
42 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
42 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
43 });
43 });
44 input.append(input_area);
44 input.append(input_area);
45 var output = $('<div></div>').addClass('output vbox');
45 var output = $('<div></div>').addClass('output vbox');
46 cell.append(input).append(output);
46 cell.append(input).append(output);
47 this.element = cell;
47 this.element = cell;
48 this.collapse();
48 this.collapse();
49 };
49 };
50
50
51 //TODO, try to diminish the number of parameters.
51 //TODO, try to diminish the number of parameters.
52 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time){
52 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time){
53 var that = this;
53 var that = this;
54 if (pre_cursor === "" || pre_cursor === "(" ) {
54 if (pre_cursor === "" || pre_cursor === "(" ) {
55 // don't do anything if line beggin with '(' or is empty
55 // don't do anything if line beggin with '(' or is empty
56 } else {
56 } else {
57 // Will set a timer to request tooltip in `time`
57 // Will set a timer to request tooltip in `time`
58 that.tooltip_timeout = setTimeout(function(){
58 that.tooltip_timeout = setTimeout(function(){
59 IPython.notebook.request_tool_tip(that, pre_cursor)
59 IPython.notebook.request_tool_tip(that, pre_cursor)
60 },time);
60 },time);
61 }
61 }
62 };
62 };
63
63
64 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
64 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
65 // This method gets called in CodeMirror's onKeyDown/onKeyPress
65 // This method gets called in CodeMirror's onKeyDown/onKeyPress
66 // handlers and is used to provide custom key handling. Its return
66 // handlers and is used to provide custom key handling. Its return
67 // value is used to determine if CodeMirror should ignore the event:
67 // value is used to determine if CodeMirror should ignore the event:
68 // true = ignore, false = don't ignore.
68 // true = ignore, false = don't ignore.
69
69
70 // note that we are comparing and setting the time to wait at each key press.
70 // note that we are comparing and setting the time to wait at each key press.
71 // a better wqy might be to generate a new function on each time change and
71 // a better wqy might be to generate a new function on each time change and
72 // assign it to CodeCell.prototype.request_tooltip_after_time
72 // assign it to CodeCell.prototype.request_tooltip_after_time
73 tooltip_wait_time = this.notebook.time_before_tooltip;
73 tooltip_wait_time = this.notebook.time_before_tooltip;
74 tooltip_on_tab = this.notebook.tooltip_on_tab;
74 tooltip_on_tab = this.notebook.tooltip_on_tab;
75 var that = this;
75 var that = this;
76 // whatever key is pressed, first, cancel the tooltip request before
76 // whatever key is pressed, first, cancel the tooltip request before
77 // they are sent, and remove tooltip if any
77 // they are sent, and remove tooltip if any
78 if(event.type === 'keydown' ){
78 if(event.type === 'keydown' ){
79 that.remove_and_cancel_tooltip();
79 that.remove_and_cancel_tooltip();
80 }
80 }
81
81
82 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
82 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
83 // Always ignore shift-enter in CodeMirror as we handle it.
83 // Always ignore shift-enter in CodeMirror as we handle it.
84 return true;
84 return true;
85 }else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
85 }else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
86 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
86 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
87 // browser and keyboard layout !
87 // browser and keyboard layout !
88 // Pressing '(' , request tooltip, don't forget to reappend it
88 // Pressing '(' , request tooltip, don't forget to reappend it
89 var cursor = editor.getCursor();
89 var cursor = editor.getCursor();
90 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
90 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
91 that.request_tooltip_after_time(pre_cursor,tooltip_wait_time);
91 that.request_tooltip_after_time(pre_cursor,tooltip_wait_time);
92 } else if (event.keyCode === 9 && event.type == 'keydown') {
92 } else if (event.keyCode === 9 && event.type == 'keydown') {
93 // Tab completion.
93 // Tab completion.
94 var cur = editor.getCursor();
94 var cur = editor.getCursor();
95 //Do not trim here because of tooltip
95 //Do not trim here because of tooltip
96 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
96 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
97 if (pre_cursor.trim() === "") {
97 if (pre_cursor.trim() === "") {
98 // Don't autocomplete if the part of the line before the cursor
98 // Don't autocomplete if the part of the line before the cursor
99 // is empty. In this case, let CodeMirror handle indentation.
99 // is empty. In this case, let CodeMirror handle indentation.
100 return false;
100 return false;
101 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
101 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
102 that.request_tooltip_after_time(pre_cursor,0);
102 that.request_tooltip_after_time(pre_cursor,0);
103 } else {
103 } else {
104 pre_cursor.trim();
104 pre_cursor.trim();
105 // Autocomplete the current line.
105 // Autocomplete the current line.
106 event.stop();
106 event.stop();
107 var line = editor.getLine(cur.line);
107 var line = editor.getLine(cur.line);
108 this.is_completing = true;
108 this.is_completing = true;
109 this.completion_cursor = cur;
109 this.completion_cursor = cur;
110 IPython.notebook.complete_cell(this, line, cur.ch);
110 IPython.notebook.complete_cell(this, line, cur.ch);
111 return true;
111 return true;
112 }
112 }
113 } else if (event.keyCode === 8 && event.type == 'keydown') {
113 } else if (event.keyCode === 8 && event.type == 'keydown') {
114 // If backspace and the line ends with 4 spaces, remove them.
114 // If backspace and the line ends with 4 spaces, remove them.
115 var cur = editor.getCursor();
115 var cur = editor.getCursor();
116 var line = editor.getLine(cur.line);
116 var line = editor.getLine(cur.line);
117 var ending = line.slice(-4);
117 var ending = line.slice(-4);
118 if (ending === ' ') {
118 if (ending === ' ') {
119 editor.replaceRange('',
119 editor.replaceRange('',
120 {line: cur.line, ch: cur.ch-4},
120 {line: cur.line, ch: cur.ch-4},
121 {line: cur.line, ch: cur.ch}
121 {line: cur.line, ch: cur.ch}
122 );
122 );
123 event.stop();
123 event.stop();
124 return true;
124 return true;
125 } else {
125 } else {
126 return false;
126 return false;
127 }
127 }
128 } else if (event.keyCode === 76 && event.ctrlKey && event.shiftKey
128 } else if (event.keyCode === 76 && event.ctrlKey && event.shiftKey
129 && event.type == 'keydown') {
129 && event.type == 'keydown') {
130 // toggle line numbers with Ctrl-Shift-L
130 // toggle line numbers with Ctrl-Shift-L
131 this.toggle_line_numbers();
131 this.toggle_line_numbers();
132 }
132 }
133 else {
133 else {
134 // keypress/keyup also trigger on TAB press, and we don't want to
134 // keypress/keyup also trigger on TAB press, and we don't want to
135 // use those to disable tab completion.
135 // use those to disable tab completion.
136 if (this.is_completing && event.keyCode !== 9) {
136 if (this.is_completing && event.keyCode !== 9) {
137 var ed_cur = editor.getCursor();
137 var ed_cur = editor.getCursor();
138 var cc_cur = this.completion_cursor;
138 var cc_cur = this.completion_cursor;
139 if (ed_cur.line !== cc_cur.line || ed_cur.ch !== cc_cur.ch) {
139 if (ed_cur.line !== cc_cur.line || ed_cur.ch !== cc_cur.ch) {
140 this.is_completing = false;
140 this.is_completing = false;
141 this.completion_cursor = null;
141 this.completion_cursor = null;
142 }
142 }
143 }
143 }
144 return false;
144 return false;
145 };
145 };
146 return false;
146 return false;
147 };
147 };
148
148
149 CodeCell.prototype.remove_and_cancel_tooltip = function() {
149 CodeCell.prototype.remove_and_cancel_tooltip = function() {
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 (this.tooltip_timeout != null){
153 if (this.tooltip_timeout != null){
154 clearTimeout(this.tooltip_timeout);
154 clearTimeout(this.tooltip_timeout);
155 $('#tooltip').remove();
155 $('#tooltip').remove();
156 this.tooltip_timeout = null;
156 this.tooltip_timeout = null;
157 }
157 }
158 }
158 }
159
159
160 CodeCell.prototype.finish_tooltip = function (reply) {
160 CodeCell.prototype.finish_tooltip = function (reply) {
161 defstring=reply.definition;
161 defstring=reply.definition;
162 docstring=reply.docstring;
162 docstring=reply.docstring;
163 if(docstring == null){docstring="<empty docstring>"};
163 if(docstring == null){docstring="<empty docstring>"};
164 name=reply.name;
164 name=reply.name;
165
165
166 var that = this;
166 var that = this;
167 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
167 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
168 // remove to have the tooltip not Limited in X and Y
168 // remove to have the tooltip not Limited in X and Y
169 tooltip.addClass('smalltooltip');
169 tooltip.addClass('smalltooltip');
170 var pre=$('<pre/>').html(utils.fixConsole(docstring));
170 var pre=$('<pre/>').html(utils.fixConsole(docstring));
171 var expandlink=$('<a/>').attr('href',"#");
171 var expandlink=$('<a/>').attr('href',"#");
172 expandlink.addClass("ui-corner-all"); //rounded corner
172 expandlink.addClass("ui-corner-all"); //rounded corner
173 expandlink.attr('role',"button");
173 expandlink.attr('role',"button");
174 //expandlink.addClass('ui-button');
174 //expandlink.addClass('ui-button');
175 //expandlink.addClass('ui-state-default');
175 //expandlink.addClass('ui-state-default');
176 var expandspan=$('<span/>').text('Expand');
176 var expandspan=$('<span/>').text('Expand');
177 expandspan.addClass('ui-icon');
177 expandspan.addClass('ui-icon');
178 expandspan.addClass('ui-icon-plus');
178 expandspan.addClass('ui-icon-plus');
179 expandlink.append(expandspan);
179 expandlink.append(expandspan);
180 expandlink.attr('id','expanbutton');
180 expandlink.attr('id','expanbutton');
181 expandlink.click(function(){
181 expandlink.click(function(){
182 tooltip.removeClass('smalltooltip');
182 tooltip.removeClass('smalltooltip');
183 tooltip.addClass('bigtooltip');
183 tooltip.addClass('bigtooltip');
184 $('#expanbutton').remove();
184 $('#expanbutton').remove();
185 setTimeout(function(){that.code_mirror.focus();}, 50);
185 setTimeout(function(){that.code_mirror.focus();}, 50);
186 });
186 });
187 var morelink=$('<a/>').attr('href',"#");
187 var morelink=$('<a/>').attr('href',"#");
188 morelink.attr('role',"button");
188 morelink.attr('role',"button");
189 morelink.addClass('ui-button');
189 morelink.addClass('ui-button');
190 //morelink.addClass("ui-corner-all"); //rounded corner
190 //morelink.addClass("ui-corner-all"); //rounded corner
191 //morelink.addClass('ui-state-default');
191 //morelink.addClass('ui-state-default');
192 var morespan=$('<span/>').text('Open in Pager');
192 var morespan=$('<span/>').text('Open in Pager');
193 morespan.addClass('ui-icon');
193 morespan.addClass('ui-icon');
194 morespan.addClass('ui-icon-arrowstop-l-n');
194 morespan.addClass('ui-icon-arrowstop-l-n');
195 morelink.append(morespan);
195 morelink.append(morespan);
196 morelink.click(function(){
196 morelink.click(function(){
197 var msg_id = IPython.notebook.kernel.execute(name+"?");
197 var msg_id = IPython.notebook.kernel.execute(name+"?");
198 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.selected_cell().cell_id;
198 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.selected_cell().cell_id;
199 that.remove_and_cancel_tooltip();
199 that.remove_and_cancel_tooltip();
200 setTimeout(function(){that.code_mirror.focus();}, 50);
200 setTimeout(function(){that.code_mirror.focus();}, 50);
201 });
201 });
202
202
203 var closelink=$('<a/>').attr('href',"#");
203 var closelink=$('<a/>').attr('href',"#");
204 closelink.attr('role',"button");
204 closelink.attr('role',"button");
205 closelink.addClass('ui-button');
205 closelink.addClass('ui-button');
206 //closelink.addClass("ui-corner-all"); //rounded corner
206 //closelink.addClass("ui-corner-all"); //rounded corner
207 //closelink.adClass('ui-state-default'); // grey background and blue cross
207 //closelink.adClass('ui-state-default'); // grey background and blue cross
208 var closespan=$('<span/>').text('Close');
208 var closespan=$('<span/>').text('Close');
209 closespan.addClass('ui-icon');
209 closespan.addClass('ui-icon');
210 closespan.addClass('ui-icon-close');
210 closespan.addClass('ui-icon-close');
211 closelink.append(closespan);
211 closelink.append(closespan);
212 closelink.click(function(){
212 closelink.click(function(){
213 that.remove_and_cancel_tooltip();
213 that.remove_and_cancel_tooltip();
214 setTimeout(function(){that.code_mirror.focus();}, 50);
214 setTimeout(function(){that.code_mirror.focus();}, 50);
215 });
215 });
216 //construct the tooltip
216 //construct the tooltip
217 tooltip.append(closelink);
217 tooltip.append(closelink);
218 tooltip.append(expandlink);
218 tooltip.append(expandlink);
219 tooltip.append(morelink);
219 tooltip.append(morelink);
220 if(defstring){
220 if(defstring){
221 defstring_html= $('<pre/>').html(utils.fixConsole(defstring));
221 defstring_html= $('<pre/>').html(utils.fixConsole(defstring));
222 tooltip.append(defstring_html);
222 tooltip.append(defstring_html);
223 }
223 }
224 tooltip.append(pre);
224 tooltip.append(pre);
225 var pos = this.code_mirror.cursorCoords();
225 var pos = this.code_mirror.cursorCoords();
226 tooltip.css('left',pos.x+'px');
226 tooltip.css('left',pos.x+'px');
227 tooltip.css('top',pos.yBot+'px');
227 tooltip.css('top',pos.yBot+'px');
228 $('body').append(tooltip);
228 $('body').append(tooltip);
229
229
230 // issues with cross-closing if multiple tooltip in less than 5sec
230 // issues with cross-closing if multiple tooltip in less than 5sec
231 // keep it comented for now
231 // keep it comented for now
232 // setTimeout(that.remove_and_cancel_tooltip, 5000);
232 // setTimeout(that.remove_and_cancel_tooltip, 5000);
233 };
233 };
234
234
235 // As you type completer
235 // As you type completer
236 CodeCell.prototype.finish_completing = function (matched_text, matches) {
236 CodeCell.prototype.finish_completing = function (matched_text, matches) {
237 //return if not completing or nothing to complete
237 //return if not completing or nothing to complete
238 if (!this.is_completing || matches.length === 0) {return;}
238 if (!this.is_completing || matches.length === 0) {return;}
239
239
240 // for later readability
240 // for later readability
241 var key = { tab:9,
241 var key = { tab:9,
242 esc:27,
242 esc:27,
243 backspace:8,
243 backspace:8,
244 space:13,
244 space:13,
245 shift:16,
245 shift:16,
246 enter:32,
246 enter:32,
247 // _ is 189
247 // _ is 189
248 isCompSymbol : function (code)
248 isCompSymbol : function (code)
249 {return ((code>64 && code <=122)|| code == 189)}
249 {return ((code>64 && code <=122)|| code == 189)}
250 }
250 }
251
251
252 // smart completion, sort kwarg ending with '='
252 // smart completion, sort kwarg ending with '='
253 var newm = new Array();
253 var newm = new Array();
254 if(this.notebook.smart_completer)
254 if(this.notebook.smart_completer)
255 {
255 {
256 kwargs = new Array();
256 kwargs = new Array();
257 other = new Array();
257 other = new Array();
258 for(var i=0;i<matches.length; ++i){
258 for(var i=0;i<matches.length; ++i){
259 if(matches[i].substr(-1) === '='){
259 if(matches[i].substr(-1) === '='){
260 kwargs.push(matches[i]);
260 kwargs.push(matches[i]);
261 }else{other.push(matches[i]);}
261 }else{other.push(matches[i]);}
262 }
262 }
263 newm = kwargs.concat(other);
263 newm = kwargs.concat(other);
264 matches=newm;
264 matches=newm;
265 }
265 }
266 // end sort kwargs
266 // end sort kwargs
267
267
268 // give common prefix of a array of string
268 // give common prefix of a array of string
269 function sharedStart(A){
269 function sharedStart(A){
270 if(A.length > 1 ){
270 if(A.length > 1 ){
271 var tem1, tem2, s, A= A.slice(0).sort();
271 var tem1, tem2, s, A= A.slice(0).sort();
272 tem1= A[0];
272 tem1= A[0];
273 s= tem1.length;
273 s= tem1.length;
274 tem2= A.pop();
274 tem2= A.pop();
275 while(s && tem2.indexOf(tem1)== -1){
275 while(s && tem2.indexOf(tem1)== -1){
276 tem1= tem1.substring(0, --s);
276 tem1= tem1.substring(0, --s);
277 }
277 }
278 return tem1;
278 return tem1;
279 }
279 }
280 return "";
280 return "";
281 }
281 }
282
282
283
283
284 //try to check if the user is typing tab at least twice after a word
284 //try to check if the user is typing tab at least twice after a word
285 // and completion is "done"
285 // and completion is "done"
286 fallback_on_tooltip_after=2
286 fallback_on_tooltip_after=2
287 if(matches.length==1 && matched_text === matches[0])
287 if(matches.length==1 && matched_text === matches[0])
288 {
288 {
289 if(this.npressed >fallback_on_tooltip_after && this.prevmatch==matched_text)
289 if(this.npressed >fallback_on_tooltip_after && this.prevmatch==matched_text)
290 {
290 {
291 console.log('Ok, you really want to complete after pressing tab '+this.npressed+' times !');
291 console.log('Ok, you really want to complete after pressing tab '+this.npressed+' times !');
292 console.log('You should understand that there is no (more) completion for that !');
292 console.log('You should understand that there is no (more) completion for that !');
293 console.log("I'll show you the tooltip, will you stop bothering me ?");
293 console.log("I'll show you the tooltip, will you stop bothering me ?");
294 this.request_tooltip_after_time(matched_text+'(',0);
294 this.request_tooltip_after_time(matched_text+'(',0);
295 return;
295 return;
296 }
296 }
297 this.prevmatch=matched_text
297 this.prevmatch=matched_text
298 this.npressed=this.npressed+1;
298 this.npressed=this.npressed+1;
299 }
299 }
300 else
300 else
301 {
301 {
302 this.prevmatch="";
302 this.prevmatch="";
303 this.npressed=0;
303 this.npressed=0;
304 }
304 }
305 // end fallback on tooltip
305 // end fallback on tooltip
306 //==================================
306 //==================================
307 // Real completion logic start here
307 // Real completion logic start here
308 var that = this;
308 var that = this;
309 var cur = this.completion_cursor;
309 var cur = this.completion_cursor;
310 var done = false;
310 var done = false;
311
311
312 // call to dismmiss the completer
312 // call to dismmiss the completer
313 var close = function () {
313 var close = function () {
314 if (done) return;
314 if (done) return;
315 done = true;
315 done = true;
316 if (complete!=undefined)
316 if (complete!=undefined)
317 {complete.remove();}
317 {complete.remove();}
318 that.is_completing = false;
318 that.is_completing = false;
319 that.completion_cursor = null;
319 that.completion_cursor = null;
320 };
320 };
321
321
322 // insert the given text and exit the completer
322 // insert the given text and exit the completer
323 var insert = function (selected_text, event) {
323 var insert = function (selected_text, event) {
324 that.code_mirror.replaceRange(
324 that.code_mirror.replaceRange(
325 selected_text,
325 selected_text,
326 {line: cur.line, ch: (cur.ch-matched_text.length)},
326 {line: cur.line, ch: (cur.ch-matched_text.length)},
327 {line: cur.line, ch: cur.ch}
327 {line: cur.line, ch: cur.ch}
328 );
328 );
329 if(event != null){
329 if(event != null){
330 event.stopPropagation();
330 event.stopPropagation();
331 event.preventDefault();
331 event.preventDefault();
332 }
332 }
333 close();
333 close();
334 setTimeout(function(){that.code_mirror.focus();}, 50);
334 setTimeout(function(){that.code_mirror.focus();}, 50);
335 };
335 };
336
336
337 // insert the curent highlited selection and exit
337 // insert the curent highlited selection and exit
338 var pick = function () {
338 var pick = function () {
339 insert(select.val()[0],null);
339 insert(select.val()[0],null);
340 };
340 };
341
341
342
342
343 // Define function to clear the completer, refill it with the new
343 // Define function to clear the completer, refill it with the new
344 // matches, update the pseuso typing field. autopick insert match if
344 // matches, update the pseuso typing field. autopick insert match if
345 // only one left, in no matches (anymore) dismiss itself by pasting
345 // only one left, in no matches (anymore) dismiss itself by pasting
346 // what the user have typed until then
346 // what the user have typed until then
347 var complete_with = function(matches,typed_text,autopick,event)
347 var complete_with = function(matches,typed_text,autopick,event)
348 {
348 {
349 // If autopick an only one match, past.
349 // If autopick an only one match, past.
350 // Used to 'pick' when pressing tab
350 // Used to 'pick' when pressing tab
351 if (matches.length < 1) {
351 if (matches.length < 1) {
352 insert(typed_text,event);
352 insert(typed_text,event);
353 if(event !=null){
353 if(event !=null){
354 event.stopPropagation();
354 event.stopPropagation();
355 event.preventDefault();
355 event.preventDefault();
356 }
356 }
357 } else if (autopick && matches.length==1) {
357 } else if (autopick && matches.length==1) {
358 insert(matches[0],event);
358 insert(matches[0],event);
359 if(event !=null){
359 if(event !=null){
360 event.stopPropagation();
360 event.stopPropagation();
361 event.preventDefault();
361 event.preventDefault();
362 }
362 }
363 }
363 }
364 //clear the previous completion if any
364 //clear the previous completion if any
365 complete.children().children().remove();
365 complete.children().children().remove();
366 $('#asyoutype').text(typed_text);
366 $('#asyoutype').text(typed_text);
367 select=$('#asyoutypeselect');
367 select=$('#asyoutypeselect');
368 for (var i=0; i<matches.length; ++i) {
368 for (var i=0; i<matches.length; ++i) {
369 select.append($('<option/>').html(matches[i]));
369 select.append($('<option/>').html(matches[i]));
370 }
370 }
371 select.children().first().attr('selected','true');
371 select.children().first().attr('selected','true');
372 }
372 }
373
373
374 // create html for completer
374 // create html for completer
375 var complete = $('<div/>').addClass('completions');
375 var complete = $('<div/>').addClass('completions');
376 complete.attr('id','complete');
376 complete.attr('id','complete');
377 complete.append($('<p/>').attr('id', 'asyoutype').html(matched_text));//pseudo input field
377 complete.append($('<p/>').attr('id', 'asyoutype').html(matched_text));//pseudo input field
378
378
379 var select = $('<select/>').attr('multiple','true');
379 var select = $('<select/>').attr('multiple','true');
380 select.attr('id', 'asyoutypeselect')
380 select.attr('id', 'asyoutypeselect')
381 select.attr('size',Math.min(10,matches.length));
381 select.attr('size',Math.min(10,matches.length));
382 var pos = this.code_mirror.cursorCoords();
382 var pos = this.code_mirror.cursorCoords();
383
383
384 // TODO: I propose to remove enough horizontal pixel
384 // TODO: I propose to remove enough horizontal pixel
385 // to align the text later
385 // to align the text later
386 complete.css('left',pos.x+'px');
386 complete.css('left',pos.x+'px');
387 complete.css('top',pos.yBot+'px');
387 complete.css('top',pos.yBot+'px');
388 complete.append(select);
388 complete.append(select);
389
389
390 $('body').append(complete);
390 $('body').append(complete);
391
391
392 // So a first actual completion. see if all the completion start wit
392 // So a first actual completion. see if all the completion start wit
393 // the same letter and complete if necessary
393 // the same letter and complete if necessary
394 fastForward = sharedStart(matches)
394 fastForward = sharedStart(matches)
395 typed_characters= fastForward.substr(matched_text.length);
395 typed_characters= fastForward.substr(matched_text.length);
396 complete_with(matches,matched_text+typed_characters,true,null);
396 complete_with(matches,matched_text+typed_characters,true,null);
397 filterd=matches;
397 filterd=matches;
398 // Give focus to select, and make it filter the match as the user type
398 // Give focus to select, and make it filter the match as the user type
399 // by filtering the previous matches. Called by .keypress and .keydown
399 // by filtering the previous matches. Called by .keypress and .keydown
400 var downandpress = function (event,press_or_down) {
400 var downandpress = function (event,press_or_down) {
401 var code = event.which;
401 var code = event.which;
402 var autopick = false; // auto 'pick' if only one match
402 var autopick = false; // auto 'pick' if only one match
403 if (press_or_down === 0){
403 if (press_or_down === 0){
404 press=true; down=false; //Are we called from keypress or keydown
404 press=true; down=false; //Are we called from keypress or keydown
405 } else if (press_or_down == 1){
405 } else if (press_or_down == 1){
406 press=false; down=true;
406 press=false; down=true;
407 }
407 }
408 if (code === key.shift) {
408 if (code === key.shift) {
409 // nothing on Shift
409 // nothing on Shift
410 return;
410 return;
411 }
411 }
412 if (code === key.space || code === key.enter) {
412 if (code === key.space || code === key.enter) {
413 // Pressing SPACE or ENTER will cause a pick
413 // Pressing SPACE or ENTER will cause a pick
414 event.stopPropagation();
414 event.stopPropagation();
415 event.preventDefault();
415 event.preventDefault();
416 pick();
416 pick();
417 } else if (code === 38 || code === 40) {
417 } else if (code === 38 || code === 40) {
418 // We don't want the document keydown handler to handle UP/DOWN,
418 // We don't want the document keydown handler to handle UP/DOWN,
419 // but we want the default action.
419 // but we want the default action.
420 event.stopPropagation();
420 event.stopPropagation();
421 //} else if ( key.isCompSymbol(code)|| (code==key.backspace)||(code==key.tab && down)){
421 //} else if ( key.isCompSymbol(code)|| (code==key.backspace)||(code==key.tab && down)){
422 } else if ( (code==key.backspace)||(code==key.tab && down) || press || key.isCompSymbol(code)){
422 } else if ( (code==key.backspace)||(code==key.tab && down) || press || key.isCompSymbol(code)){
423 if( key.isCompSymbol(code) && press)
423 if( key.isCompSymbol(code) && press)
424 {
424 {
425 var newchar = String.fromCharCode(code);
425 var newchar = String.fromCharCode(code);
426 typed_characters=typed_characters+newchar;
426 typed_characters=typed_characters+newchar;
427 } else if (code == key.tab) {
427 } else if (code == key.tab) {
428 fastForward = sharedStart(filterd)
428 fastForward = sharedStart(filterd)
429 ffsub = fastForward.substr(matched_text.length+typed_characters.length);
429 ffsub = fastForward.substr(matched_text.length+typed_characters.length);
430 typed_characters=typed_characters+ffsub;
430 typed_characters=typed_characters+ffsub;
431 autopick=true;
431 autopick=true;
432 event.stopPropagation();
432 event.stopPropagation();
433 event.preventDefault();
433 event.preventDefault();
434 } else if (code == key.backspace && down) {
434 } else if (code == key.backspace && down) {
435 // cancel if user have erase everything, otherwise decrease
435 // cancel if user have erase everything, otherwise decrease
436 // what we filter with
436 // what we filter with
437 if (typed_characters.length <= 0)
437 if (typed_characters.length <= 0)
438 {
438 {
439 insert(matched_text,event)
439 insert(matched_text,event)
440 }
440 }
441 typed_characters=typed_characters.substr(0,typed_characters.length-1);
441 typed_characters=typed_characters.substr(0,typed_characters.length-1);
442 }else{return}
442 }else{return}
443 re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
443 re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
444 filterd = matches.filter(function(x){return re.test(x)});
444 filterd = matches.filter(function(x){return re.test(x)});
445 complete_with(filterd,matched_text+typed_characters,autopick,event);
445 complete_with(filterd,matched_text+typed_characters,autopick,event);
446 } else if(down){ // abort only on .keydown
446 } else if(down){ // abort only on .keydown
447 // abort with what the user have pressed until now
447 // abort with what the user have pressed until now
448 console.log('aborting with keycode : '+code+' is down :'+down);
448 console.log('aborting with keycode : '+code+' is down :'+down);
449 insert(matched_text+typed_characters,event);
449 insert(matched_text+typed_characters,event);
450 }
450 }
451 }
451 }
452 select.keydown(function (event) {
452 select.keydown(function (event) {
453 downandpress(event,1)
453 downandpress(event,1)
454 });
454 });
455 select.keypress(function (event) {
455 select.keypress(function (event) {
456 downandpress(event,0)
456 downandpress(event,0)
457 });
457 });
458 // Double click also causes a pick.
458 // Double click also causes a pick.
459 // and bind the last actions.
459 // and bind the last actions.
460 select.dblclick(pick);
460 select.dblclick(pick);
461 select.blur(close);
461 select.blur(close);
462 select.focus();
462 select.focus();
463 };
463 };
464
464
465 CodeCell.prototype.toggle_line_numbers = function () {
465 CodeCell.prototype.toggle_line_numbers = function () {
466 if (this.code_mirror.getOption('lineNumbers') == false) {
466 if (this.code_mirror.getOption('lineNumbers') == false) {
467 this.code_mirror.setOption('lineNumbers', true);
467 this.code_mirror.setOption('lineNumbers', true);
468 } else {
468 } else {
469 this.code_mirror.setOption('lineNumbers', false);
469 this.code_mirror.setOption('lineNumbers', false);
470 }
470 }
471 this.code_mirror.refresh();
471 this.code_mirror.refresh();
472 };
472 };
473
473
474 CodeCell.prototype.select = function () {
474 CodeCell.prototype.select = function () {
475 IPython.Cell.prototype.select.apply(this);
475 IPython.Cell.prototype.select.apply(this);
476 // Todo: this dance is needed because as of CodeMirror 2.12, focus is
476 // Todo: this dance is needed because as of CodeMirror 2.12, focus is
477 // not causing the cursor to blink if the editor is empty initially.
477 // not causing the cursor to blink if the editor is empty initially.
478 // While this seems to fix the issue, this should be fixed
478 // While this seems to fix the issue, this should be fixed
479 // in CodeMirror proper.
479 // in CodeMirror proper.
480 var s = this.code_mirror.getValue();
480 var s = this.code_mirror.getValue();
481 this.code_mirror.focus();
481 this.code_mirror.focus();
482 if (s === '') this.code_mirror.setValue('');
482 if (s === '') this.code_mirror.setValue('');
483 };
483 };
484
484
485
485
486 CodeCell.prototype.select_all = function () {
486 CodeCell.prototype.select_all = function () {
487 var start = {line: 0, ch: 0};
487 var start = {line: 0, ch: 0};
488 var nlines = this.code_mirror.lineCount();
488 var nlines = this.code_mirror.lineCount();
489 var last_line = this.code_mirror.getLine(nlines-1);
489 var last_line = this.code_mirror.getLine(nlines-1);
490 var end = {line: nlines-1, ch: last_line.length};
490 var end = {line: nlines-1, ch: last_line.length};
491 this.code_mirror.setSelection(start, end);
491 this.code_mirror.setSelection(start, end);
492 };
492 };
493
493
494
494
495 CodeCell.prototype.append_output = function (json) {
495 CodeCell.prototype.append_output = function (json) {
496 this.expand();
496 this.expand();
497 if (json.output_type === 'pyout') {
497 if (json.output_type === 'pyout') {
498 this.append_pyout(json);
498 this.append_pyout(json);
499 } else if (json.output_type === 'pyerr') {
499 } else if (json.output_type === 'pyerr') {
500 this.append_pyerr(json);
500 this.append_pyerr(json);
501 } else if (json.output_type === 'display_data') {
501 } else if (json.output_type === 'display_data') {
502 this.append_display_data(json);
502 this.append_display_data(json);
503 } else if (json.output_type === 'stream') {
503 } else if (json.output_type === 'stream') {
504 this.append_stream(json);
504 this.append_stream(json);
505 };
505 };
506 this.outputs.push(json);
506 this.outputs.push(json);
507 };
507 };
508
508
509
509
510 CodeCell.prototype.create_output_area = function () {
510 CodeCell.prototype.create_output_area = function () {
511 var oa = $("<div/>").addClass("hbox output_area");
511 var oa = $("<div/>").addClass("hbox output_area");
512 oa.append($('<div/>').addClass('prompt'));
512 oa.append($('<div/>').addClass('prompt'));
513 return oa;
513 return oa;
514 };
514 };
515
515
516
516
517 CodeCell.prototype.append_pyout = function (json) {
517 CodeCell.prototype.append_pyout = function (json) {
518 n = json.prompt_number || ' ';
518 n = json.prompt_number || ' ';
519 var toinsert = this.create_output_area();
519 var toinsert = this.create_output_area();
520 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
520 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
521 this.append_mime_type(json, toinsert);
521 this.append_mime_type(json, toinsert);
522 this.element.find('div.output').append(toinsert);
522 this.element.find('div.output').append(toinsert);
523 // If we just output latex, typeset it.
523 // If we just output latex, typeset it.
524 if ((json.latex !== undefined) || (json.html !== undefined)) {
524 if ((json.latex !== undefined) || (json.html !== undefined)) {
525 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
525 this.typeset();
526 };
526 };
527 };
527 };
528
528
529
529
530 CodeCell.prototype.append_pyerr = function (json) {
530 CodeCell.prototype.append_pyerr = function (json) {
531 var tb = json.traceback;
531 var tb = json.traceback;
532 if (tb !== undefined && tb.length > 0) {
532 if (tb !== undefined && tb.length > 0) {
533 var s = '';
533 var s = '';
534 var len = tb.length;
534 var len = tb.length;
535 for (var i=0; i<len; i++) {
535 for (var i=0; i<len; i++) {
536 s = s + tb[i] + '\n';
536 s = s + tb[i] + '\n';
537 }
537 }
538 s = s + '\n';
538 s = s + '\n';
539 var toinsert = this.create_output_area();
539 var toinsert = this.create_output_area();
540 this.append_text(s, toinsert);
540 this.append_text(s, toinsert);
541 this.element.find('div.output').append(toinsert);
541 this.element.find('div.output').append(toinsert);
542 };
542 };
543 };
543 };
544
544
545
545
546 CodeCell.prototype.append_stream = function (json) {
546 CodeCell.prototype.append_stream = function (json) {
547 // temporary fix: if stream undefined (json file written prior to this patch),
547 // temporary fix: if stream undefined (json file written prior to this patch),
548 // default to most likely stdout:
548 // default to most likely stdout:
549 if (json.stream == undefined){
549 if (json.stream == undefined){
550 json.stream = 'stdout';
550 json.stream = 'stdout';
551 }
551 }
552 var subclass = "output_"+json.stream;
552 var subclass = "output_"+json.stream;
553 if (this.outputs.length > 0){
553 if (this.outputs.length > 0){
554 // have at least one output to consider
554 // have at least one output to consider
555 var last = this.outputs[this.outputs.length-1];
555 var last = this.outputs[this.outputs.length-1];
556 if (last.output_type == 'stream' && json.stream == last.stream){
556 if (last.output_type == 'stream' && json.stream == last.stream){
557 // latest output was in the same stream,
557 // latest output was in the same stream,
558 // so append directly into its pre tag
558 // so append directly into its pre tag
559 this.element.find('div.'+subclass).last().find('pre').append(json.text);
559 this.element.find('div.'+subclass).last().find('pre').append(json.text);
560 return;
560 return;
561 }
561 }
562 }
562 }
563
563
564 // If we got here, attach a new div
564 // If we got here, attach a new div
565 var toinsert = this.create_output_area();
565 var toinsert = this.create_output_area();
566 this.append_text(json.text, toinsert, "output_stream "+subclass);
566 this.append_text(json.text, toinsert, "output_stream "+subclass);
567 this.element.find('div.output').append(toinsert);
567 this.element.find('div.output').append(toinsert);
568 };
568 };
569
569
570
570
571 CodeCell.prototype.append_display_data = function (json) {
571 CodeCell.prototype.append_display_data = function (json) {
572 var toinsert = this.create_output_area();
572 var toinsert = this.create_output_area();
573 this.append_mime_type(json, toinsert);
573 this.append_mime_type(json, toinsert);
574 this.element.find('div.output').append(toinsert);
574 this.element.find('div.output').append(toinsert);
575 // If we just output latex, typeset it.
575 // If we just output latex, typeset it.
576 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
576 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
577 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
577 this.typeset();
578 };
578 };
579 };
579 };
580
580
581
581
582 CodeCell.prototype.append_mime_type = function (json, element) {
582 CodeCell.prototype.append_mime_type = function (json, element) {
583 if (json.html !== undefined) {
583 if (json.html !== undefined) {
584 this.append_html(json.html, element);
584 this.append_html(json.html, element);
585 } else if (json.latex !== undefined) {
585 } else if (json.latex !== undefined) {
586 this.append_latex(json.latex, element);
586 this.append_latex(json.latex, element);
587 } else if (json.svg !== undefined) {
587 } else if (json.svg !== undefined) {
588 this.append_svg(json.svg, element);
588 this.append_svg(json.svg, element);
589 } else if (json.png !== undefined) {
589 } else if (json.png !== undefined) {
590 this.append_png(json.png, element);
590 this.append_png(json.png, element);
591 } else if (json.jpeg !== undefined) {
591 } else if (json.jpeg !== undefined) {
592 this.append_jpeg(json.jpeg, element);
592 this.append_jpeg(json.jpeg, element);
593 } else if (json.text !== undefined) {
593 } else if (json.text !== undefined) {
594 this.append_text(json.text, element);
594 this.append_text(json.text, element);
595 };
595 };
596 };
596 };
597
597
598
598
599 CodeCell.prototype.append_html = function (html, element) {
599 CodeCell.prototype.append_html = function (html, element) {
600 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
600 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
601 toinsert.append(html);
601 toinsert.append(html);
602 element.append(toinsert);
602 element.append(toinsert);
603 };
603 };
604
604
605
605
606 CodeCell.prototype.append_text = function (data, element, extra_class) {
606 CodeCell.prototype.append_text = function (data, element, extra_class) {
607 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
607 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
608 if (extra_class){
608 if (extra_class){
609 toinsert.addClass(extra_class);
609 toinsert.addClass(extra_class);
610 }
610 }
611 toinsert.append($("<pre/>").html(data));
611 toinsert.append($("<pre/>").html(data));
612 element.append(toinsert);
612 element.append(toinsert);
613 };
613 };
614
614
615
615
616 CodeCell.prototype.append_svg = function (svg, element) {
616 CodeCell.prototype.append_svg = function (svg, element) {
617 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
617 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
618 toinsert.append(svg);
618 toinsert.append(svg);
619 element.append(toinsert);
619 element.append(toinsert);
620 };
620 };
621
621
622
622
623 CodeCell.prototype.append_png = function (png, element) {
623 CodeCell.prototype.append_png = function (png, element) {
624 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
624 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
625 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
625 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
626 element.append(toinsert);
626 element.append(toinsert);
627 };
627 };
628
628
629
629
630 CodeCell.prototype.append_jpeg = function (jpeg, element) {
630 CodeCell.prototype.append_jpeg = function (jpeg, element) {
631 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
631 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
632 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
632 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
633 element.append(toinsert);
633 element.append(toinsert);
634 };
634 };
635
635
636
636
637 CodeCell.prototype.append_latex = function (latex, element) {
637 CodeCell.prototype.append_latex = function (latex, element) {
638 // This method cannot do the typesetting because the latex first has to
638 // This method cannot do the typesetting because the latex first has to
639 // be on the page.
639 // be on the page.
640 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
640 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
641 toinsert.append(latex);
641 toinsert.append(latex);
642 element.append(toinsert);
642 element.append(toinsert);
643 };
643 };
644
644
645
645
646 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
646 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
647 var output_div = this.element.find("div.output");
647 var output_div = this.element.find("div.output");
648 if (stdout && stderr && other){
648 if (stdout && stderr && other){
649 // clear all, no need for logic
649 // clear all, no need for logic
650 output_div.html("");
650 output_div.html("");
651 this.outputs = [];
651 this.outputs = [];
652 return;
652 return;
653 }
653 }
654 // remove html output
654 // remove html output
655 // each output_subarea that has an identifying class is in an output_area
655 // each output_subarea that has an identifying class is in an output_area
656 // which is the element to be removed.
656 // which is the element to be removed.
657 if (stdout){
657 if (stdout){
658 output_div.find("div.output_stdout").parent().remove();
658 output_div.find("div.output_stdout").parent().remove();
659 }
659 }
660 if (stderr){
660 if (stderr){
661 output_div.find("div.output_stderr").parent().remove();
661 output_div.find("div.output_stderr").parent().remove();
662 }
662 }
663 if (other){
663 if (other){
664 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
664 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
665 }
665 }
666
666
667 // remove cleared outputs from JSON list:
667 // remove cleared outputs from JSON list:
668 for (var i = this.outputs.length - 1; i >= 0; i--){
668 for (var i = this.outputs.length - 1; i >= 0; i--){
669 var out = this.outputs[i];
669 var out = this.outputs[i];
670 var output_type = out.output_type;
670 var output_type = out.output_type;
671 if (output_type == "display_data" && other){
671 if (output_type == "display_data" && other){
672 this.outputs.splice(i,1);
672 this.outputs.splice(i,1);
673 }else if (output_type == "stream"){
673 }else if (output_type == "stream"){
674 if (stdout && out.stream == "stdout"){
674 if (stdout && out.stream == "stdout"){
675 this.outputs.splice(i,1);
675 this.outputs.splice(i,1);
676 }else if (stderr && out.stream == "stderr"){
676 }else if (stderr && out.stream == "stderr"){
677 this.outputs.splice(i,1);
677 this.outputs.splice(i,1);
678 }
678 }
679 }
679 }
680 }
680 }
681 };
681 };
682
682
683
683
684 CodeCell.prototype.clear_input = function () {
684 CodeCell.prototype.clear_input = function () {
685 this.code_mirror.setValue('');
685 this.code_mirror.setValue('');
686 };
686 };
687
687
688
688
689 CodeCell.prototype.collapse = function () {
689 CodeCell.prototype.collapse = function () {
690 if (!this.collapsed) {
690 if (!this.collapsed) {
691 this.element.find('div.output').hide();
691 this.element.find('div.output').hide();
692 this.collapsed = true;
692 this.collapsed = true;
693 };
693 };
694 };
694 };
695
695
696
696
697 CodeCell.prototype.expand = function () {
697 CodeCell.prototype.expand = function () {
698 if (this.collapsed) {
698 if (this.collapsed) {
699 this.element.find('div.output').show();
699 this.element.find('div.output').show();
700 this.collapsed = false;
700 this.collapsed = false;
701 };
701 };
702 };
702 };
703
703
704
704
705 CodeCell.prototype.toggle_output = function () {
705 CodeCell.prototype.toggle_output = function () {
706 if (this.collapsed) {
706 if (this.collapsed) {
707 this.expand();
707 this.expand();
708 } else {
708 } else {
709 this.collapse();
709 this.collapse();
710 };
710 };
711 };
711 };
712
712
713 CodeCell.prototype.set_input_prompt = function (number) {
713 CodeCell.prototype.set_input_prompt = function (number) {
714 var n = number || '&nbsp;';
714 var n = number || '&nbsp;';
715 this.input_prompt_number = n;
715 this.input_prompt_number = n;
716 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
716 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
717 };
717 };
718
718
719
719
720 CodeCell.prototype.get_code = function () {
720 CodeCell.prototype.get_code = function () {
721 return this.code_mirror.getValue();
721 return this.code_mirror.getValue();
722 };
722 };
723
723
724
724
725 CodeCell.prototype.set_code = function (code) {
725 CodeCell.prototype.set_code = function (code) {
726 return this.code_mirror.setValue(code);
726 return this.code_mirror.setValue(code);
727 };
727 };
728
728
729
729
730 CodeCell.prototype.at_top = function () {
730 CodeCell.prototype.at_top = function () {
731 var cursor = this.code_mirror.getCursor();
731 var cursor = this.code_mirror.getCursor();
732 if (cursor.line === 0) {
732 if (cursor.line === 0) {
733 return true;
733 return true;
734 } else {
734 } else {
735 return false;
735 return false;
736 }
736 }
737 };
737 };
738
738
739
739
740 CodeCell.prototype.at_bottom = function () {
740 CodeCell.prototype.at_bottom = function () {
741 var cursor = this.code_mirror.getCursor();
741 var cursor = this.code_mirror.getCursor();
742 if (cursor.line === (this.code_mirror.lineCount()-1)) {
742 if (cursor.line === (this.code_mirror.lineCount()-1)) {
743 return true;
743 return true;
744 } else {
744 } else {
745 return false;
745 return false;
746 }
746 }
747 };
747 };
748
748
749
749
750 CodeCell.prototype.fromJSON = function (data) {
750 CodeCell.prototype.fromJSON = function (data) {
751 console.log('Import from JSON:', data);
751 console.log('Import from JSON:', data);
752 if (data.cell_type === 'code') {
752 if (data.cell_type === 'code') {
753 if (data.input !== undefined) {
753 if (data.input !== undefined) {
754 this.set_code(data.input);
754 this.set_code(data.input);
755 }
755 }
756 if (data.prompt_number !== undefined) {
756 if (data.prompt_number !== undefined) {
757 this.set_input_prompt(data.prompt_number);
757 this.set_input_prompt(data.prompt_number);
758 } else {
758 } else {
759 this.set_input_prompt();
759 this.set_input_prompt();
760 };
760 };
761 var len = data.outputs.length;
761 var len = data.outputs.length;
762 for (var i=0; i<len; i++) {
762 for (var i=0; i<len; i++) {
763 this.append_output(data.outputs[i]);
763 this.append_output(data.outputs[i]);
764 };
764 };
765 if (data.collapsed !== undefined) {
765 if (data.collapsed !== undefined) {
766 if (data.collapsed) {
766 if (data.collapsed) {
767 this.collapse();
767 this.collapse();
768 };
768 };
769 };
769 };
770 };
770 };
771 };
771 };
772
772
773
773
774 CodeCell.prototype.toJSON = function () {
774 CodeCell.prototype.toJSON = function () {
775 var data = {};
775 var data = {};
776 data.input = this.get_code();
776 data.input = this.get_code();
777 data.cell_type = 'code';
777 data.cell_type = 'code';
778 if (this.input_prompt_number !== ' ') {
778 if (this.input_prompt_number !== ' ') {
779 data.prompt_number = this.input_prompt_number;
779 data.prompt_number = this.input_prompt_number;
780 };
780 };
781 var outputs = [];
781 var outputs = [];
782 var len = this.outputs.length;
782 var len = this.outputs.length;
783 for (var i=0; i<len; i++) {
783 for (var i=0; i<len; i++) {
784 outputs[i] = this.outputs[i];
784 outputs[i] = this.outputs[i];
785 };
785 };
786 data.outputs = outputs;
786 data.outputs = outputs;
787 data.language = 'python';
787 data.language = 'python';
788 data.collapsed = this.collapsed;
788 data.collapsed = this.collapsed;
789 // console.log('Export to JSON:',data);
789 // console.log('Export to JSON:',data);
790 return data;
790 return data;
791 };
791 };
792
792
793
793
794 IPython.CodeCell = CodeCell;
794 IPython.CodeCell = CodeCell;
795
795
796 return IPython;
796 return IPython;
797 }(IPython));
797 }(IPython));
@@ -1,1079 +1,1079 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 // Notebook
9 // Notebook
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 Notebook = function (selector) {
16 var Notebook = function (selector) {
17 this.read_only = IPython.read_only;
17 this.read_only = IPython.read_only;
18 this.element = $(selector);
18 this.element = $(selector);
19 this.element.scroll();
19 this.element.scroll();
20 this.element.data("notebook", this);
20 this.element.data("notebook", this);
21 this.next_prompt_number = 1;
21 this.next_prompt_number = 1;
22 this.kernel = null;
22 this.kernel = null;
23 this.dirty = false;
23 this.dirty = false;
24 this.msg_cell_map = {};
24 this.msg_cell_map = {};
25 this.metadata = {};
25 this.metadata = {};
26 this.control_key_active = false;
26 this.control_key_active = false;
27 this.style();
27 this.style();
28 this.create_elements();
28 this.create_elements();
29 this.bind_events();
29 this.bind_events();
30 this.set_tooltipontab(true);
30 this.set_tooltipontab(true);
31 this.set_smartcompleter(true);
31 this.set_smartcompleter(true);
32 this.set_timebeforetooltip(1200);
32 this.set_timebeforetooltip(1200);
33 };
33 };
34
34
35
35
36 Notebook.prototype.style = function () {
36 Notebook.prototype.style = function () {
37 $('div#notebook').addClass('border-box-sizing');
37 $('div#notebook').addClass('border-box-sizing');
38 };
38 };
39
39
40
40
41 Notebook.prototype.create_elements = function () {
41 Notebook.prototype.create_elements = function () {
42 // We add this end_space div to the end of the notebook div to:
42 // We add this end_space div to the end of the notebook div to:
43 // i) provide a margin between the last cell and the end of the notebook
43 // i) provide a margin between the last cell and the end of the notebook
44 // ii) to prevent the div from scrolling up when the last cell is being
44 // ii) to prevent the div from scrolling up when the last cell is being
45 // edited, but is too low on the page, which browsers will do automatically.
45 // edited, but is too low on the page, which browsers will do automatically.
46 var that = this;
46 var that = this;
47 var end_space = $('<div class="end_space"></div>').height("30%");
47 var end_space = $('<div class="end_space"></div>').height("30%");
48 end_space.dblclick(function (e) {
48 end_space.dblclick(function (e) {
49 if (that.read_only) return;
49 if (that.read_only) return;
50 var ncells = that.ncells();
50 var ncells = that.ncells();
51 that.insert_code_cell_below(ncells-1);
51 that.insert_code_cell_below(ncells-1);
52 });
52 });
53 this.element.append(end_space);
53 this.element.append(end_space);
54 $('div#notebook').addClass('border-box-sizing');
54 $('div#notebook').addClass('border-box-sizing');
55 };
55 };
56
56
57
57
58 Notebook.prototype.bind_events = function () {
58 Notebook.prototype.bind_events = function () {
59 var that = this;
59 var that = this;
60 $(document).keydown(function (event) {
60 $(document).keydown(function (event) {
61 // console.log(event);
61 // console.log(event);
62 if (that.read_only) return false;
62 if (that.read_only) return true;
63 if (event.which === 27) {
63 if (event.which === 27) {
64 // Intercept escape at highest level to avoid closing
64 // Intercept escape at highest level to avoid closing
65 // websocket connection with firefox
65 // websocket connection with firefox
66 event.preventDefault();
66 event.preventDefault();
67 }
67 }
68 if (event.which === 38 && !event.shiftKey) {
68 if (event.which === 38 && !event.shiftKey) {
69 var cell = that.selected_cell();
69 var cell = that.selected_cell();
70 if (cell.at_top()) {
70 if (cell.at_top()) {
71 event.preventDefault();
71 event.preventDefault();
72 that.select_prev();
72 that.select_prev();
73 };
73 };
74 } else if (event.which === 40 && !event.shiftKey) {
74 } else if (event.which === 40 && !event.shiftKey) {
75 var cell = that.selected_cell();
75 var cell = that.selected_cell();
76 if (cell.at_bottom()) {
76 if (cell.at_bottom()) {
77 event.preventDefault();
77 event.preventDefault();
78 that.select_next();
78 that.select_next();
79 };
79 };
80 } else if (event.which === 13 && event.shiftKey) {
80 } else if (event.which === 13 && event.shiftKey) {
81 that.execute_selected_cell();
81 that.execute_selected_cell();
82 return false;
82 return false;
83 } else if (event.which === 13 && event.ctrlKey) {
83 } else if (event.which === 13 && event.ctrlKey) {
84 that.execute_selected_cell({terminal:true});
84 that.execute_selected_cell({terminal:true});
85 return false;
85 return false;
86 } else if (event.which === 77 && event.ctrlKey) {
86 } else if (event.which === 77 && event.ctrlKey) {
87 that.control_key_active = true;
87 that.control_key_active = true;
88 return false;
88 return false;
89 } else if (event.which === 68 && that.control_key_active) {
89 } else if (event.which === 68 && that.control_key_active) {
90 // Delete selected cell = d
90 // Delete selected cell = d
91 that.delete_cell();
91 that.delete_cell();
92 that.control_key_active = false;
92 that.control_key_active = false;
93 return false;
93 return false;
94 } else if (event.which === 65 && that.control_key_active) {
94 } else if (event.which === 65 && that.control_key_active) {
95 // Insert code cell above selected = a
95 // Insert code cell above selected = a
96 that.insert_code_cell_above();
96 that.insert_code_cell_above();
97 that.control_key_active = false;
97 that.control_key_active = false;
98 return false;
98 return false;
99 } else if (event.which === 66 && that.control_key_active) {
99 } else if (event.which === 66 && that.control_key_active) {
100 // Insert code cell below selected = b
100 // Insert code cell below selected = b
101 that.insert_code_cell_below();
101 that.insert_code_cell_below();
102 that.control_key_active = false;
102 that.control_key_active = false;
103 return false;
103 return false;
104 } else if (event.which === 67 && that.control_key_active) {
104 } else if (event.which === 67 && that.control_key_active) {
105 // To code = c
105 // To code = c
106 that.to_code();
106 that.to_code();
107 that.control_key_active = false;
107 that.control_key_active = false;
108 return false;
108 return false;
109 } else if (event.which === 77 && that.control_key_active) {
109 } else if (event.which === 77 && that.control_key_active) {
110 // To markdown = m
110 // To markdown = m
111 that.to_markdown();
111 that.to_markdown();
112 that.control_key_active = false;
112 that.control_key_active = false;
113 return false;
113 return false;
114 } else if (event.which === 84 && that.control_key_active) {
114 } else if (event.which === 84 && that.control_key_active) {
115 // Toggle output = t
115 // Toggle output = t
116 that.toggle_output();
116 that.toggle_output();
117 that.control_key_active = false;
117 that.control_key_active = false;
118 return false;
118 return false;
119 } else if (event.which === 83 && that.control_key_active) {
119 } else if (event.which === 83 && that.control_key_active) {
120 // Save notebook = s
120 // Save notebook = s
121 IPython.save_widget.save_notebook();
121 IPython.save_widget.save_notebook();
122 that.control_key_active = false;
122 that.control_key_active = false;
123 return false;
123 return false;
124 } else if (event.which === 74 && that.control_key_active) {
124 } else if (event.which === 74 && that.control_key_active) {
125 // Move cell down = j
125 // Move cell down = j
126 that.move_cell_down();
126 that.move_cell_down();
127 that.control_key_active = false;
127 that.control_key_active = false;
128 return false;
128 return false;
129 } else if (event.which === 75 && that.control_key_active) {
129 } else if (event.which === 75 && that.control_key_active) {
130 // Move cell up = k
130 // Move cell up = k
131 that.move_cell_up();
131 that.move_cell_up();
132 that.control_key_active = false;
132 that.control_key_active = false;
133 return false;
133 return false;
134 } else if (event.which === 80 && that.control_key_active) {
134 } else if (event.which === 80 && that.control_key_active) {
135 // Select previous = p
135 // Select previous = p
136 that.select_prev();
136 that.select_prev();
137 that.control_key_active = false;
137 that.control_key_active = false;
138 return false;
138 return false;
139 } else if (event.which === 78 && that.control_key_active) {
139 } else if (event.which === 78 && that.control_key_active) {
140 // Select next = n
140 // Select next = n
141 that.select_next();
141 that.select_next();
142 that.control_key_active = false;
142 that.control_key_active = false;
143 return false;
143 return false;
144 } else if (event.which === 76 && that.control_key_active) {
144 } else if (event.which === 76 && that.control_key_active) {
145 // Toggle line numbers = l
145 // Toggle line numbers = l
146 that.cell_toggle_line_numbers();
146 that.cell_toggle_line_numbers();
147 that.control_key_active = false;
147 that.control_key_active = false;
148 return false;
148 return false;
149 } else if (event.which === 73 && that.control_key_active) {
149 } else if (event.which === 73 && that.control_key_active) {
150 // Interrupt kernel = i
150 // Interrupt kernel = i
151 IPython.notebook.kernel.interrupt();
151 IPython.notebook.kernel.interrupt();
152 that.control_key_active = false;
152 that.control_key_active = false;
153 return false;
153 return false;
154 } else if (event.which === 190 && that.control_key_active) {
154 } else if (event.which === 190 && that.control_key_active) {
155 // Restart kernel = . # matches qt console
155 // Restart kernel = . # matches qt console
156 IPython.notebook.restart_kernel();
156 IPython.notebook.restart_kernel();
157 that.control_key_active = false;
157 that.control_key_active = false;
158 return false;
158 return false;
159 } else if (event.which === 72 && that.control_key_active) {
159 } else if (event.which === 72 && that.control_key_active) {
160 // Show keyboard shortcuts = h
160 // Show keyboard shortcuts = h
161 that.toggle_keyboard_shortcuts();
161 that.toggle_keyboard_shortcuts();
162 that.control_key_active = false;
162 that.control_key_active = false;
163 return false;
163 return false;
164 } else if (that.control_key_active) {
164 } else if (that.control_key_active) {
165 that.control_key_active = false;
165 that.control_key_active = false;
166 return true;
166 return true;
167 };
167 };
168 return true;
168 return true;
169 });
169 });
170
170
171 this.element.bind('collapse_pager', function () {
171 this.element.bind('collapse_pager', function () {
172 var app_height = $('div#main_app').height(); // content height
172 var app_height = $('div#main_app').height(); // content height
173 var splitter_height = $('div#pager_splitter').outerHeight(true);
173 var splitter_height = $('div#pager_splitter').outerHeight(true);
174 var new_height = app_height - splitter_height;
174 var new_height = app_height - splitter_height;
175 that.element.animate({height : new_height + 'px'}, 'fast');
175 that.element.animate({height : new_height + 'px'}, 'fast');
176 });
176 });
177
177
178 this.element.bind('expand_pager', function () {
178 this.element.bind('expand_pager', function () {
179 var app_height = $('div#main_app').height(); // content height
179 var app_height = $('div#main_app').height(); // content height
180 var splitter_height = $('div#pager_splitter').outerHeight(true);
180 var splitter_height = $('div#pager_splitter').outerHeight(true);
181 var pager_height = $('div#pager').outerHeight(true);
181 var pager_height = $('div#pager').outerHeight(true);
182 var new_height = app_height - pager_height - splitter_height;
182 var new_height = app_height - pager_height - splitter_height;
183 that.element.animate({height : new_height + 'px'}, 'fast');
183 that.element.animate({height : new_height + 'px'}, 'fast');
184 });
184 });
185
185
186 this.element.bind('collapse_left_panel', function () {
186 this.element.bind('collapse_left_panel', function () {
187 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
187 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
188 var new_margin = splitter_width;
188 var new_margin = splitter_width;
189 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
189 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
190 });
190 });
191
191
192 this.element.bind('expand_left_panel', function () {
192 this.element.bind('expand_left_panel', function () {
193 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
193 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
194 var left_panel_width = IPython.left_panel.width;
194 var left_panel_width = IPython.left_panel.width;
195 var new_margin = splitter_width + left_panel_width;
195 var new_margin = splitter_width + left_panel_width;
196 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
196 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
197 });
197 });
198
198
199 $(window).bind('beforeunload', function () {
199 $(window).bind('beforeunload', function () {
200 var kill_kernel = $('#kill_kernel').prop('checked');
200 var kill_kernel = $('#kill_kernel').prop('checked');
201 if (kill_kernel) {
201 if (kill_kernel) {
202 that.kernel.kill();
202 that.kernel.kill();
203 }
203 }
204 if (that.dirty && ! that.read_only) {
204 if (that.dirty && ! that.read_only) {
205 return "You have unsaved changes that will be lost if you leave this page.";
205 return "You have unsaved changes that will be lost if you leave this page.";
206 };
206 };
207 // Null is the *only* return value that will make the browser not
207 // Null is the *only* return value that will make the browser not
208 // pop up the "don't leave" dialog.
208 // pop up the "don't leave" dialog.
209 return null;
209 return null;
210 });
210 });
211 };
211 };
212
212
213
213
214 Notebook.prototype.toggle_keyboard_shortcuts = function () {
214 Notebook.prototype.toggle_keyboard_shortcuts = function () {
215 // toggles display of keyboard shortcut dialog
215 // toggles display of keyboard shortcut dialog
216 var that = this;
216 var that = this;
217 if ( this.shortcut_dialog ){
217 if ( this.shortcut_dialog ){
218 // if dialog is already shown, close it
218 // if dialog is already shown, close it
219 this.shortcut_dialog.dialog("close");
219 this.shortcut_dialog.dialog("close");
220 this.shortcut_dialog = null;
220 this.shortcut_dialog = null;
221 return;
221 return;
222 }
222 }
223 var dialog = $('<div/>');
223 var dialog = $('<div/>');
224 this.shortcut_dialog = dialog;
224 this.shortcut_dialog = dialog;
225 var shortcuts = [
225 var shortcuts = [
226 {key: 'Shift-Enter', help: 'run cell'},
226 {key: 'Shift-Enter', help: 'run cell'},
227 {key: 'Ctrl-Enter', help: 'run cell in-place'},
227 {key: 'Ctrl-Enter', help: 'run cell in-place'},
228 {key: 'Ctrl-m d', help: 'delete cell'},
228 {key: 'Ctrl-m d', help: 'delete cell'},
229 {key: 'Ctrl-m a', help: 'insert cell above'},
229 {key: 'Ctrl-m a', help: 'insert cell above'},
230 {key: 'Ctrl-m b', help: 'insert cell below'},
230 {key: 'Ctrl-m b', help: 'insert cell below'},
231 {key: 'Ctrl-m t', help: 'toggle output'},
231 {key: 'Ctrl-m t', help: 'toggle output'},
232 {key: 'Ctrl-m l', help: 'toggle line numbers'},
232 {key: 'Ctrl-m l', help: 'toggle line numbers'},
233 {key: 'Ctrl-m s', help: 'save notebook'},
233 {key: 'Ctrl-m s', help: 'save notebook'},
234 {key: 'Ctrl-m j', help: 'move cell down'},
234 {key: 'Ctrl-m j', help: 'move cell down'},
235 {key: 'Ctrl-m k', help: 'move cell up'},
235 {key: 'Ctrl-m k', help: 'move cell up'},
236 {key: 'Ctrl-m c', help: 'code cell'},
236 {key: 'Ctrl-m c', help: 'code cell'},
237 {key: 'Ctrl-m m', help: 'markdown cell'},
237 {key: 'Ctrl-m m', help: 'markdown cell'},
238 {key: 'Ctrl-m p', help: 'select previous'},
238 {key: 'Ctrl-m p', help: 'select previous'},
239 {key: 'Ctrl-m n', help: 'select next'},
239 {key: 'Ctrl-m n', help: 'select next'},
240 {key: 'Ctrl-m i', help: 'interrupt kernel'},
240 {key: 'Ctrl-m i', help: 'interrupt kernel'},
241 {key: 'Ctrl-m .', help: 'restart kernel'},
241 {key: 'Ctrl-m .', help: 'restart kernel'},
242 {key: 'Ctrl-m h', help: 'show keyboard shortcuts'}
242 {key: 'Ctrl-m h', help: 'show keyboard shortcuts'}
243 ];
243 ];
244 for (var i=0; i<shortcuts.length; i++) {
244 for (var i=0; i<shortcuts.length; i++) {
245 dialog.append($('<div>').
245 dialog.append($('<div>').
246 append($('<span/>').addClass('shortcut_key').html(shortcuts[i].key)).
246 append($('<span/>').addClass('shortcut_key').html(shortcuts[i].key)).
247 append($('<span/>').addClass('shortcut_descr').html(' : ' + shortcuts[i].help))
247 append($('<span/>').addClass('shortcut_descr').html(' : ' + shortcuts[i].help))
248 );
248 );
249 };
249 };
250 dialog.bind('dialogclose', function(event) {
250 dialog.bind('dialogclose', function(event) {
251 // dialog has been closed, allow it to be drawn again.
251 // dialog has been closed, allow it to be drawn again.
252 that.shortcut_dialog = null;
252 that.shortcut_dialog = null;
253 });
253 });
254 dialog.dialog({title: 'Keyboard shortcuts'});
254 dialog.dialog({title: 'Keyboard shortcuts'});
255 };
255 };
256
256
257
257
258 Notebook.prototype.scroll_to_bottom = function () {
258 Notebook.prototype.scroll_to_bottom = function () {
259 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
259 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
260 };
260 };
261
261
262
262
263 Notebook.prototype.scroll_to_top = function () {
263 Notebook.prototype.scroll_to_top = function () {
264 this.element.animate({scrollTop:0}, 0);
264 this.element.animate({scrollTop:0}, 0);
265 };
265 };
266
266
267
267
268 // Cell indexing, retrieval, etc.
268 // Cell indexing, retrieval, etc.
269
269
270
270
271 Notebook.prototype.cell_elements = function () {
271 Notebook.prototype.cell_elements = function () {
272 return this.element.children("div.cell");
272 return this.element.children("div.cell");
273 };
273 };
274
274
275
275
276 Notebook.prototype.ncells = function (cell) {
276 Notebook.prototype.ncells = function (cell) {
277 return this.cell_elements().length;
277 return this.cell_elements().length;
278 };
278 };
279
279
280
280
281 // TODO: we are often calling cells as cells()[i], which we should optimize
281 // TODO: we are often calling cells as cells()[i], which we should optimize
282 // to cells(i) or a new method.
282 // to cells(i) or a new method.
283 Notebook.prototype.cells = function () {
283 Notebook.prototype.cells = function () {
284 return this.cell_elements().toArray().map(function (e) {
284 return this.cell_elements().toArray().map(function (e) {
285 return $(e).data("cell");
285 return $(e).data("cell");
286 });
286 });
287 };
287 };
288
288
289
289
290 Notebook.prototype.find_cell_index = function (cell) {
290 Notebook.prototype.find_cell_index = function (cell) {
291 var result = null;
291 var result = null;
292 this.cell_elements().filter(function (index) {
292 this.cell_elements().filter(function (index) {
293 if ($(this).data("cell") === cell) {
293 if ($(this).data("cell") === cell) {
294 result = index;
294 result = index;
295 };
295 };
296 });
296 });
297 return result;
297 return result;
298 };
298 };
299
299
300
300
301 Notebook.prototype.index_or_selected = function (index) {
301 Notebook.prototype.index_or_selected = function (index) {
302 return index || this.selected_index() || 0;
302 return index || this.selected_index() || 0;
303 };
303 };
304
304
305
305
306 Notebook.prototype.select = function (index) {
306 Notebook.prototype.select = function (index) {
307 if (index !== undefined && index >= 0 && index < this.ncells()) {
307 if (index !== undefined && index >= 0 && index < this.ncells()) {
308 if (this.selected_index() !== null) {
308 if (this.selected_index() !== null) {
309 this.selected_cell().unselect();
309 this.selected_cell().unselect();
310 };
310 };
311 this.cells()[index].select();
311 this.cells()[index].select();
312 };
312 };
313 return this;
313 return this;
314 };
314 };
315
315
316
316
317 Notebook.prototype.select_next = function () {
317 Notebook.prototype.select_next = function () {
318 var index = this.selected_index();
318 var index = this.selected_index();
319 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
319 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
320 this.select(index+1);
320 this.select(index+1);
321 };
321 };
322 return this;
322 return this;
323 };
323 };
324
324
325
325
326 Notebook.prototype.select_prev = function () {
326 Notebook.prototype.select_prev = function () {
327 var index = this.selected_index();
327 var index = this.selected_index();
328 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
328 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
329 this.select(index-1);
329 this.select(index-1);
330 };
330 };
331 return this;
331 return this;
332 };
332 };
333
333
334
334
335 Notebook.prototype.selected_index = function () {
335 Notebook.prototype.selected_index = function () {
336 var result = null;
336 var result = null;
337 this.cell_elements().filter(function (index) {
337 this.cell_elements().filter(function (index) {
338 if ($(this).data("cell").selected === true) {
338 if ($(this).data("cell").selected === true) {
339 result = index;
339 result = index;
340 };
340 };
341 });
341 });
342 return result;
342 return result;
343 };
343 };
344
344
345
345
346 Notebook.prototype.cell_for_msg = function (msg_id) {
346 Notebook.prototype.cell_for_msg = function (msg_id) {
347 var cell_id = this.msg_cell_map[msg_id];
347 var cell_id = this.msg_cell_map[msg_id];
348 var result = null;
348 var result = null;
349 this.cell_elements().filter(function (index) {
349 this.cell_elements().filter(function (index) {
350 cell = $(this).data("cell");
350 cell = $(this).data("cell");
351 if (cell.cell_id === cell_id) {
351 if (cell.cell_id === cell_id) {
352 result = cell;
352 result = cell;
353 };
353 };
354 });
354 });
355 return result;
355 return result;
356 };
356 };
357
357
358
358
359 Notebook.prototype.selected_cell = function () {
359 Notebook.prototype.selected_cell = function () {
360 return this.cell_elements().eq(this.selected_index()).data("cell");
360 return this.cell_elements().eq(this.selected_index()).data("cell");
361 };
361 };
362
362
363
363
364 // Cell insertion, deletion and moving.
364 // Cell insertion, deletion and moving.
365
365
366
366
367 Notebook.prototype.delete_cell = function (index) {
367 Notebook.prototype.delete_cell = function (index) {
368 var i = index || this.selected_index();
368 var i = index || this.selected_index();
369 if (i !== null && i >= 0 && i < this.ncells()) {
369 if (i !== null && i >= 0 && i < this.ncells()) {
370 this.cell_elements().eq(i).remove();
370 this.cell_elements().eq(i).remove();
371 if (i === (this.ncells())) {
371 if (i === (this.ncells())) {
372 this.select(i-1);
372 this.select(i-1);
373 } else {
373 } else {
374 this.select(i);
374 this.select(i);
375 };
375 };
376 };
376 };
377 this.dirty = true;
377 this.dirty = true;
378 return this;
378 return this;
379 };
379 };
380
380
381
381
382 Notebook.prototype.append_cell = function (cell) {
382 Notebook.prototype.append_cell = function (cell) {
383 this.element.find('div.end_space').before(cell.element);
383 this.element.find('div.end_space').before(cell.element);
384 this.dirty = true;
384 this.dirty = true;
385 return this;
385 return this;
386 };
386 };
387
387
388
388
389 Notebook.prototype.insert_cell_below = function (cell, index) {
389 Notebook.prototype.insert_cell_below = function (cell, index) {
390 var ncells = this.ncells();
390 var ncells = this.ncells();
391 if (ncells === 0) {
391 if (ncells === 0) {
392 this.append_cell(cell);
392 this.append_cell(cell);
393 return this;
393 return this;
394 };
394 };
395 if (index >= 0 && index < ncells) {
395 if (index >= 0 && index < ncells) {
396 this.cell_elements().eq(index).after(cell.element);
396 this.cell_elements().eq(index).after(cell.element);
397 };
397 };
398 this.dirty = true;
398 this.dirty = true;
399 return this;
399 return this;
400 };
400 };
401
401
402
402
403 Notebook.prototype.insert_cell_above = function (cell, index) {
403 Notebook.prototype.insert_cell_above = function (cell, index) {
404 var ncells = this.ncells();
404 var ncells = this.ncells();
405 if (ncells === 0) {
405 if (ncells === 0) {
406 this.append_cell(cell);
406 this.append_cell(cell);
407 return this;
407 return this;
408 };
408 };
409 if (index >= 0 && index < ncells) {
409 if (index >= 0 && index < ncells) {
410 this.cell_elements().eq(index).before(cell.element);
410 this.cell_elements().eq(index).before(cell.element);
411 };
411 };
412 this.dirty = true;
412 this.dirty = true;
413 return this;
413 return this;
414 };
414 };
415
415
416
416
417 Notebook.prototype.move_cell_up = function (index) {
417 Notebook.prototype.move_cell_up = function (index) {
418 var i = index || this.selected_index();
418 var i = index || this.selected_index();
419 if (i !== null && i < this.ncells() && i > 0) {
419 if (i !== null && i < this.ncells() && i > 0) {
420 var pivot = this.cell_elements().eq(i-1);
420 var pivot = this.cell_elements().eq(i-1);
421 var tomove = this.cell_elements().eq(i);
421 var tomove = this.cell_elements().eq(i);
422 if (pivot !== null && tomove !== null) {
422 if (pivot !== null && tomove !== null) {
423 tomove.detach();
423 tomove.detach();
424 pivot.before(tomove);
424 pivot.before(tomove);
425 this.select(i-1);
425 this.select(i-1);
426 };
426 };
427 };
427 };
428 this.dirty = true;
428 this.dirty = true;
429 return this;
429 return this;
430 };
430 };
431
431
432
432
433 Notebook.prototype.move_cell_down = function (index) {
433 Notebook.prototype.move_cell_down = function (index) {
434 var i = index || this.selected_index();
434 var i = index || this.selected_index();
435 if (i !== null && i < (this.ncells()-1) && i >= 0) {
435 if (i !== null && i < (this.ncells()-1) && i >= 0) {
436 var pivot = this.cell_elements().eq(i+1);
436 var pivot = this.cell_elements().eq(i+1);
437 var tomove = this.cell_elements().eq(i);
437 var tomove = this.cell_elements().eq(i);
438 if (pivot !== null && tomove !== null) {
438 if (pivot !== null && tomove !== null) {
439 tomove.detach();
439 tomove.detach();
440 pivot.after(tomove);
440 pivot.after(tomove);
441 this.select(i+1);
441 this.select(i+1);
442 };
442 };
443 };
443 };
444 this.dirty = true;
444 this.dirty = true;
445 return this;
445 return this;
446 };
446 };
447
447
448
448
449 Notebook.prototype.sort_cells = function () {
449 Notebook.prototype.sort_cells = function () {
450 var ncells = this.ncells();
450 var ncells = this.ncells();
451 var sindex = this.selected_index();
451 var sindex = this.selected_index();
452 var swapped;
452 var swapped;
453 do {
453 do {
454 swapped = false;
454 swapped = false;
455 for (var i=1; i<ncells; i++) {
455 for (var i=1; i<ncells; i++) {
456 current = this.cell_elements().eq(i).data("cell");
456 current = this.cell_elements().eq(i).data("cell");
457 previous = this.cell_elements().eq(i-1).data("cell");
457 previous = this.cell_elements().eq(i-1).data("cell");
458 if (previous.input_prompt_number > current.input_prompt_number) {
458 if (previous.input_prompt_number > current.input_prompt_number) {
459 this.move_cell_up(i);
459 this.move_cell_up(i);
460 swapped = true;
460 swapped = true;
461 };
461 };
462 };
462 };
463 } while (swapped);
463 } while (swapped);
464 this.select(sindex);
464 this.select(sindex);
465 return this;
465 return this;
466 };
466 };
467
467
468
468
469 Notebook.prototype.insert_code_cell_above = function (index) {
469 Notebook.prototype.insert_code_cell_above = function (index) {
470 // TODO: Bounds check for i
470 // TODO: Bounds check for i
471 var i = this.index_or_selected(index);
471 var i = this.index_or_selected(index);
472 var cell = new IPython.CodeCell(this);
472 var cell = new IPython.CodeCell(this);
473 cell.set_input_prompt();
473 cell.set_input_prompt();
474 this.insert_cell_above(cell, i);
474 this.insert_cell_above(cell, i);
475 this.select(this.find_cell_index(cell));
475 this.select(this.find_cell_index(cell));
476 return cell;
476 return cell;
477 };
477 };
478
478
479
479
480 Notebook.prototype.insert_code_cell_below = function (index) {
480 Notebook.prototype.insert_code_cell_below = function (index) {
481 // TODO: Bounds check for i
481 // TODO: Bounds check for i
482 var i = this.index_or_selected(index);
482 var i = this.index_or_selected(index);
483 var cell = new IPython.CodeCell(this);
483 var cell = new IPython.CodeCell(this);
484 cell.set_input_prompt();
484 cell.set_input_prompt();
485 this.insert_cell_below(cell, i);
485 this.insert_cell_below(cell, i);
486 this.select(this.find_cell_index(cell));
486 this.select(this.find_cell_index(cell));
487 return cell;
487 return cell;
488 };
488 };
489
489
490
490
491 Notebook.prototype.insert_html_cell_above = function (index) {
491 Notebook.prototype.insert_html_cell_above = function (index) {
492 // TODO: Bounds check for i
492 // TODO: Bounds check for i
493 var i = this.index_or_selected(index);
493 var i = this.index_or_selected(index);
494 var cell = new IPython.HTMLCell(this);
494 var cell = new IPython.HTMLCell(this);
495 cell.config_mathjax();
495 cell.config_mathjax();
496 this.insert_cell_above(cell, i);
496 this.insert_cell_above(cell, i);
497 this.select(this.find_cell_index(cell));
497 this.select(this.find_cell_index(cell));
498 return cell;
498 return cell;
499 };
499 };
500
500
501
501
502 Notebook.prototype.insert_html_cell_below = function (index) {
502 Notebook.prototype.insert_html_cell_below = function (index) {
503 // TODO: Bounds check for i
503 // TODO: Bounds check for i
504 var i = this.index_or_selected(index);
504 var i = this.index_or_selected(index);
505 var cell = new IPython.HTMLCell(this);
505 var cell = new IPython.HTMLCell(this);
506 cell.config_mathjax();
506 cell.config_mathjax();
507 this.insert_cell_below(cell, i);
507 this.insert_cell_below(cell, i);
508 this.select(this.find_cell_index(cell));
508 this.select(this.find_cell_index(cell));
509 return cell;
509 return cell;
510 };
510 };
511
511
512
512
513 Notebook.prototype.insert_markdown_cell_above = function (index) {
513 Notebook.prototype.insert_markdown_cell_above = function (index) {
514 // TODO: Bounds check for i
514 // TODO: Bounds check for i
515 var i = this.index_or_selected(index);
515 var i = this.index_or_selected(index);
516 var cell = new IPython.MarkdownCell(this);
516 var cell = new IPython.MarkdownCell(this);
517 cell.config_mathjax();
517 cell.config_mathjax();
518 this.insert_cell_above(cell, i);
518 this.insert_cell_above(cell, i);
519 this.select(this.find_cell_index(cell));
519 this.select(this.find_cell_index(cell));
520 return cell;
520 return cell;
521 };
521 };
522
522
523
523
524 Notebook.prototype.insert_markdown_cell_below = function (index) {
524 Notebook.prototype.insert_markdown_cell_below = function (index) {
525 // TODO: Bounds check for i
525 // TODO: Bounds check for i
526 var i = this.index_or_selected(index);
526 var i = this.index_or_selected(index);
527 var cell = new IPython.MarkdownCell(this);
527 var cell = new IPython.MarkdownCell(this);
528 cell.config_mathjax();
528 cell.config_mathjax();
529 this.insert_cell_below(cell, i);
529 this.insert_cell_below(cell, i);
530 this.select(this.find_cell_index(cell));
530 this.select(this.find_cell_index(cell));
531 return cell;
531 return cell;
532 };
532 };
533
533
534
534
535 Notebook.prototype.to_code = function (index) {
535 Notebook.prototype.to_code = function (index) {
536 // TODO: Bounds check for i
536 // TODO: Bounds check for i
537 var i = this.index_or_selected(index);
537 var i = this.index_or_selected(index);
538 var source_element = this.cell_elements().eq(i);
538 var source_element = this.cell_elements().eq(i);
539 var source_cell = source_element.data("cell");
539 var source_cell = source_element.data("cell");
540 if (source_cell instanceof IPython.HTMLCell ||
540 if (source_cell instanceof IPython.HTMLCell ||
541 source_cell instanceof IPython.MarkdownCell) {
541 source_cell instanceof IPython.MarkdownCell) {
542 this.insert_code_cell_below(i);
542 this.insert_code_cell_below(i);
543 var target_cell = this.cells()[i+1];
543 var target_cell = this.cells()[i+1];
544 target_cell.set_code(source_cell.get_source());
544 target_cell.set_code(source_cell.get_source());
545 source_element.remove();
545 source_element.remove();
546 target_cell.select();
546 target_cell.select();
547 };
547 };
548 this.dirty = true;
548 this.dirty = true;
549 };
549 };
550
550
551
551
552 Notebook.prototype.to_markdown = function (index) {
552 Notebook.prototype.to_markdown = function (index) {
553 // TODO: Bounds check for i
553 // TODO: Bounds check for i
554 var i = this.index_or_selected(index);
554 var i = this.index_or_selected(index);
555 var source_element = this.cell_elements().eq(i);
555 var source_element = this.cell_elements().eq(i);
556 var source_cell = source_element.data("cell");
556 var source_cell = source_element.data("cell");
557 var target_cell = null;
557 var target_cell = null;
558 if (source_cell instanceof IPython.CodeCell) {
558 if (source_cell instanceof IPython.CodeCell) {
559 this.insert_markdown_cell_below(i);
559 this.insert_markdown_cell_below(i);
560 target_cell = this.cells()[i+1];
560 target_cell = this.cells()[i+1];
561 var text = source_cell.get_code();
561 var text = source_cell.get_code();
562 } else if (source_cell instanceof IPython.HTMLCell) {
562 } else if (source_cell instanceof IPython.HTMLCell) {
563 this.insert_markdown_cell_below(i);
563 this.insert_markdown_cell_below(i);
564 target_cell = this.cells()[i+1];
564 target_cell = this.cells()[i+1];
565 var text = source_cell.get_source();
565 var text = source_cell.get_source();
566 if (text === source_cell.placeholder) {
566 if (text === source_cell.placeholder) {
567 text = target_cell.placeholder;
567 text = target_cell.placeholder;
568 }
568 }
569 }
569 }
570 if (target_cell !== null) {
570 if (target_cell !== null) {
571 if (text === "") {text = target_cell.placeholder;};
571 if (text === "") {text = target_cell.placeholder;};
572 target_cell.set_source(text);
572 target_cell.set_source(text);
573 source_element.remove();
573 source_element.remove();
574 target_cell.edit();
574 target_cell.edit();
575 }
575 }
576 this.dirty = true;
576 this.dirty = true;
577 };
577 };
578
578
579
579
580 Notebook.prototype.to_html = function (index) {
580 Notebook.prototype.to_html = function (index) {
581 // TODO: Bounds check for i
581 // TODO: Bounds check for i
582 var i = this.index_or_selected(index);
582 var i = this.index_or_selected(index);
583 var source_element = this.cell_elements().eq(i);
583 var source_element = this.cell_elements().eq(i);
584 var source_cell = source_element.data("cell");
584 var source_cell = source_element.data("cell");
585 var target_cell = null;
585 var target_cell = null;
586 if (source_cell instanceof IPython.CodeCell) {
586 if (source_cell instanceof IPython.CodeCell) {
587 this.insert_html_cell_below(i);
587 this.insert_html_cell_below(i);
588 target_cell = this.cells()[i+1];
588 target_cell = this.cells()[i+1];
589 var text = source_cell.get_code();
589 var text = source_cell.get_code();
590 } else if (source_cell instanceof IPython.MarkdownCell) {
590 } else if (source_cell instanceof IPython.MarkdownCell) {
591 this.insert_html_cell_below(i);
591 this.insert_html_cell_below(i);
592 target_cell = this.cells()[i+1];
592 target_cell = this.cells()[i+1];
593 var text = source_cell.get_source();
593 var text = source_cell.get_source();
594 if (text === source_cell.placeholder) {
594 if (text === source_cell.placeholder) {
595 text = target_cell.placeholder;
595 text = target_cell.placeholder;
596 }
596 }
597 }
597 }
598 if (target_cell !== null) {
598 if (target_cell !== null) {
599 if (text === "") {text = target_cell.placeholder;};
599 if (text === "") {text = target_cell.placeholder;};
600 target_cell.set_source(text);
600 target_cell.set_source(text);
601 source_element.remove();
601 source_element.remove();
602 target_cell.edit();
602 target_cell.edit();
603 }
603 }
604 this.dirty = true;
604 this.dirty = true;
605 };
605 };
606
606
607
607
608 // Cell collapsing and output clearing
608 // Cell collapsing and output clearing
609
609
610 Notebook.prototype.collapse = function (index) {
610 Notebook.prototype.collapse = function (index) {
611 var i = this.index_or_selected(index);
611 var i = this.index_or_selected(index);
612 this.cells()[i].collapse();
612 this.cells()[i].collapse();
613 this.dirty = true;
613 this.dirty = true;
614 };
614 };
615
615
616
616
617 Notebook.prototype.expand = function (index) {
617 Notebook.prototype.expand = function (index) {
618 var i = this.index_or_selected(index);
618 var i = this.index_or_selected(index);
619 this.cells()[i].expand();
619 this.cells()[i].expand();
620 this.dirty = true;
620 this.dirty = true;
621 };
621 };
622
622
623
623
624 Notebook.prototype.toggle_output = function (index) {
624 Notebook.prototype.toggle_output = function (index) {
625 var i = this.index_or_selected(index);
625 var i = this.index_or_selected(index);
626 this.cells()[i].toggle_output();
626 this.cells()[i].toggle_output();
627 this.dirty = true;
627 this.dirty = true;
628 };
628 };
629
629
630
630
631 Notebook.prototype.set_timebeforetooltip = function (time) {
631 Notebook.prototype.set_timebeforetooltip = function (time) {
632 console.log("change time before tooltip to : "+time);
632 console.log("change time before tooltip to : "+time);
633 this.time_before_tooltip = time;
633 this.time_before_tooltip = time;
634 };
634 };
635
635
636 Notebook.prototype.set_tooltipontab = function (state) {
636 Notebook.prototype.set_tooltipontab = function (state) {
637 console.log("change tooltip on tab to : "+state);
637 console.log("change tooltip on tab to : "+state);
638 this.tooltip_on_tab = state;
638 this.tooltip_on_tab = state;
639 };
639 };
640
640
641 Notebook.prototype.set_smartcompleter = function (state) {
641 Notebook.prototype.set_smartcompleter = function (state) {
642 console.log("Smart completion (kwargs first) changed to to : "+state);
642 console.log("Smart completion (kwargs first) changed to to : "+state);
643 this.smart_completer = state;
643 this.smart_completer = state;
644 };
644 };
645
645
646 Notebook.prototype.set_autoindent = function (state) {
646 Notebook.prototype.set_autoindent = function (state) {
647 var cells = this.cells();
647 var cells = this.cells();
648 len = cells.length;
648 len = cells.length;
649 for (var i=0; i<len; i++) {
649 for (var i=0; i<len; i++) {
650 cells[i].set_autoindent(state);
650 cells[i].set_autoindent(state);
651 };
651 };
652 };
652 };
653
653
654
654
655 Notebook.prototype.clear_all_output = function () {
655 Notebook.prototype.clear_all_output = function () {
656 var ncells = this.ncells();
656 var ncells = this.ncells();
657 var cells = this.cells();
657 var cells = this.cells();
658 for (var i=0; i<ncells; i++) {
658 for (var i=0; i<ncells; i++) {
659 if (cells[i] instanceof IPython.CodeCell) {
659 if (cells[i] instanceof IPython.CodeCell) {
660 cells[i].clear_output(true,true,true);
660 cells[i].clear_output(true,true,true);
661 }
661 }
662 };
662 };
663 this.dirty = true;
663 this.dirty = true;
664 };
664 };
665
665
666 // Other cell functions: line numbers, ...
666 // Other cell functions: line numbers, ...
667
667
668 Notebook.prototype.cell_toggle_line_numbers = function() {
668 Notebook.prototype.cell_toggle_line_numbers = function() {
669 this.selected_cell().toggle_line_numbers();
669 this.selected_cell().toggle_line_numbers();
670 };
670 };
671
671
672 // Kernel related things
672 // Kernel related things
673
673
674 Notebook.prototype.start_kernel = function () {
674 Notebook.prototype.start_kernel = function () {
675 this.kernel = new IPython.Kernel();
675 this.kernel = new IPython.Kernel();
676 var notebook_id = IPython.save_widget.get_notebook_id();
676 var notebook_id = IPython.save_widget.get_notebook_id();
677 this.kernel.start(notebook_id, $.proxy(this.kernel_started, this));
677 this.kernel.start(notebook_id, $.proxy(this.kernel_started, this));
678 };
678 };
679
679
680
680
681 Notebook.prototype.restart_kernel = function () {
681 Notebook.prototype.restart_kernel = function () {
682 var that = this;
682 var that = this;
683 var notebook_id = IPython.save_widget.get_notebook_id();
683 var notebook_id = IPython.save_widget.get_notebook_id();
684
684
685 var dialog = $('<div/>');
685 var dialog = $('<div/>');
686 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
686 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
687 $(document).append(dialog);
687 $(document).append(dialog);
688 dialog.dialog({
688 dialog.dialog({
689 resizable: false,
689 resizable: false,
690 modal: true,
690 modal: true,
691 title: "Restart kernel or continue running?",
691 title: "Restart kernel or continue running?",
692 buttons : {
692 buttons : {
693 "Restart": function () {
693 "Restart": function () {
694 that.kernel.restart($.proxy(that.kernel_started, that));
694 that.kernel.restart($.proxy(that.kernel_started, that));
695 $(this).dialog('close');
695 $(this).dialog('close');
696 },
696 },
697 "Continue running": function () {
697 "Continue running": function () {
698 $(this).dialog('close');
698 $(this).dialog('close');
699 }
699 }
700 }
700 }
701 });
701 });
702 };
702 };
703
703
704
704
705 Notebook.prototype.kernel_started = function () {
705 Notebook.prototype.kernel_started = function () {
706 console.log("Kernel started: ", this.kernel.kernel_id);
706 console.log("Kernel started: ", this.kernel.kernel_id);
707 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
707 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
708 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
708 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
709 };
709 };
710
710
711
711
712 Notebook.prototype.handle_shell_reply = function (e) {
712 Notebook.prototype.handle_shell_reply = function (e) {
713 reply = $.parseJSON(e.data);
713 reply = $.parseJSON(e.data);
714 var header = reply.header;
714 var header = reply.header;
715 var content = reply.content;
715 var content = reply.content;
716 var msg_type = header.msg_type;
716 var msg_type = header.msg_type;
717 // console.log(reply);
717 // console.log(reply);
718 var cell = this.cell_for_msg(reply.parent_header.msg_id);
718 var cell = this.cell_for_msg(reply.parent_header.msg_id);
719 if (msg_type === "execute_reply") {
719 if (msg_type === "execute_reply") {
720 cell.set_input_prompt(content.execution_count);
720 cell.set_input_prompt(content.execution_count);
721 cell.element.removeClass("running");
721 cell.element.removeClass("running");
722 this.dirty = true;
722 this.dirty = true;
723 } else if (msg_type === "complete_reply") {
723 } else if (msg_type === "complete_reply") {
724 cell.finish_completing(content.matched_text, content.matches);
724 cell.finish_completing(content.matched_text, content.matches);
725 } else if (msg_type === "object_info_reply"){
725 } else if (msg_type === "object_info_reply"){
726 //console.log('back from object_info_request : ')
726 //console.log('back from object_info_request : ')
727 rep = reply.content;
727 rep = reply.content;
728 if(rep.found)
728 if(rep.found)
729 {
729 {
730 cell.finish_tooltip(rep);
730 cell.finish_tooltip(rep);
731 }
731 }
732 } else {
732 } else {
733 //console.log("unknown reply:"+msg_type);
733 //console.log("unknown reply:"+msg_type);
734 }
734 }
735 // when having a rely from object_info_reply,
735 // when having a rely from object_info_reply,
736 // no payload so no nned to handle it
736 // no payload so no nned to handle it
737 if(typeof(content.payload)!='undefined') {
737 if(typeof(content.payload)!='undefined') {
738 var payload = content.payload || [];
738 var payload = content.payload || [];
739 this.handle_payload(cell, payload);
739 this.handle_payload(cell, payload);
740 }
740 }
741 };
741 };
742
742
743
743
744 Notebook.prototype.handle_payload = function (cell, payload) {
744 Notebook.prototype.handle_payload = function (cell, payload) {
745 var l = payload.length;
745 var l = payload.length;
746 for (var i=0; i<l; i++) {
746 for (var i=0; i<l; i++) {
747 if (payload[i].source === 'IPython.zmq.page.page') {
747 if (payload[i].source === 'IPython.zmq.page.page') {
748 if (payload[i].text.trim() !== '') {
748 if (payload[i].text.trim() !== '') {
749 IPython.pager.clear();
749 IPython.pager.clear();
750 IPython.pager.expand();
750 IPython.pager.expand();
751 IPython.pager.append_text(payload[i].text);
751 IPython.pager.append_text(payload[i].text);
752 }
752 }
753 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
753 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
754 var index = this.find_cell_index(cell);
754 var index = this.find_cell_index(cell);
755 var new_cell = this.insert_code_cell_below(index);
755 var new_cell = this.insert_code_cell_below(index);
756 new_cell.set_code(payload[i].text);
756 new_cell.set_code(payload[i].text);
757 this.dirty = true;
757 this.dirty = true;
758 }
758 }
759 };
759 };
760 };
760 };
761
761
762
762
763 Notebook.prototype.handle_iopub_reply = function (e) {
763 Notebook.prototype.handle_iopub_reply = function (e) {
764 reply = $.parseJSON(e.data);
764 reply = $.parseJSON(e.data);
765 var content = reply.content;
765 var content = reply.content;
766 // console.log(reply);
766 // console.log(reply);
767 var msg_type = reply.header.msg_type;
767 var msg_type = reply.header.msg_type;
768 var cell = this.cell_for_msg(reply.parent_header.msg_id);
768 var cell = this.cell_for_msg(reply.parent_header.msg_id);
769 if (!cell){
769 if (!cell){
770 // message not from this notebook
770 // message not from this notebook
771 console.log("Received IOPub message not caused by one of my cells");
771 console.log("Received IOPub message not caused by one of my cells");
772 return;
772 return;
773 }
773 }
774 var output_types = ['stream','display_data','pyout','pyerr'];
774 var output_types = ['stream','display_data','pyout','pyerr'];
775 if (output_types.indexOf(msg_type) >= 0) {
775 if (output_types.indexOf(msg_type) >= 0) {
776 this.handle_output(cell, msg_type, content);
776 this.handle_output(cell, msg_type, content);
777 } else if (msg_type === 'status') {
777 } else if (msg_type === 'status') {
778 if (content.execution_state === 'busy') {
778 if (content.execution_state === 'busy') {
779 IPython.kernel_status_widget.status_busy();
779 IPython.kernel_status_widget.status_busy();
780 } else if (content.execution_state === 'idle') {
780 } else if (content.execution_state === 'idle') {
781 IPython.kernel_status_widget.status_idle();
781 IPython.kernel_status_widget.status_idle();
782 } else if (content.execution_state === 'dead') {
782 } else if (content.execution_state === 'dead') {
783 this.handle_status_dead();
783 this.handle_status_dead();
784 };
784 };
785 } else if (msg_type === 'clear_output') {
785 } else if (msg_type === 'clear_output') {
786 cell.clear_output(content.stdout, content.stderr, content.other);
786 cell.clear_output(content.stdout, content.stderr, content.other);
787 };
787 };
788 };
788 };
789
789
790
790
791 Notebook.prototype.handle_status_dead = function () {
791 Notebook.prototype.handle_status_dead = function () {
792 var that = this;
792 var that = this;
793 this.kernel.stop_channels();
793 this.kernel.stop_channels();
794 var dialog = $('<div/>');
794 var dialog = $('<div/>');
795 dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
795 dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
796 $(document).append(dialog);
796 $(document).append(dialog);
797 dialog.dialog({
797 dialog.dialog({
798 resizable: false,
798 resizable: false,
799 modal: true,
799 modal: true,
800 title: "Dead kernel",
800 title: "Dead kernel",
801 buttons : {
801 buttons : {
802 "Restart": function () {
802 "Restart": function () {
803 that.start_kernel();
803 that.start_kernel();
804 $(this).dialog('close');
804 $(this).dialog('close');
805 },
805 },
806 "Continue running": function () {
806 "Continue running": function () {
807 $(this).dialog('close');
807 $(this).dialog('close');
808 }
808 }
809 }
809 }
810 });
810 });
811 };
811 };
812
812
813
813
814 Notebook.prototype.handle_output = function (cell, msg_type, content) {
814 Notebook.prototype.handle_output = function (cell, msg_type, content) {
815 var json = {};
815 var json = {};
816 json.output_type = msg_type;
816 json.output_type = msg_type;
817 if (msg_type === "stream") {
817 if (msg_type === "stream") {
818 json.text = utils.fixConsole(content.data);
818 json.text = utils.fixConsole(content.data);
819 json.stream = content.name;
819 json.stream = content.name;
820 } else if (msg_type === "display_data") {
820 } else if (msg_type === "display_data") {
821 json = this.convert_mime_types(json, content.data);
821 json = this.convert_mime_types(json, content.data);
822 } else if (msg_type === "pyout") {
822 } else if (msg_type === "pyout") {
823 json.prompt_number = content.execution_count;
823 json.prompt_number = content.execution_count;
824 json = this.convert_mime_types(json, content.data);
824 json = this.convert_mime_types(json, content.data);
825 } else if (msg_type === "pyerr") {
825 } else if (msg_type === "pyerr") {
826 json.ename = content.ename;
826 json.ename = content.ename;
827 json.evalue = content.evalue;
827 json.evalue = content.evalue;
828 var traceback = [];
828 var traceback = [];
829 for (var i=0; i<content.traceback.length; i++) {
829 for (var i=0; i<content.traceback.length; i++) {
830 traceback.push(utils.fixConsole(content.traceback[i]));
830 traceback.push(utils.fixConsole(content.traceback[i]));
831 }
831 }
832 json.traceback = traceback;
832 json.traceback = traceback;
833 };
833 };
834 cell.append_output(json);
834 cell.append_output(json);
835 this.dirty = true;
835 this.dirty = true;
836 };
836 };
837
837
838
838
839 Notebook.prototype.convert_mime_types = function (json, data) {
839 Notebook.prototype.convert_mime_types = function (json, data) {
840 if (data['text/plain'] !== undefined) {
840 if (data['text/plain'] !== undefined) {
841 json.text = utils.fixConsole(data['text/plain']);
841 json.text = utils.fixConsole(data['text/plain']);
842 };
842 };
843 if (data['text/html'] !== undefined) {
843 if (data['text/html'] !== undefined) {
844 json.html = data['text/html'];
844 json.html = data['text/html'];
845 };
845 };
846 if (data['image/svg+xml'] !== undefined) {
846 if (data['image/svg+xml'] !== undefined) {
847 json.svg = data['image/svg+xml'];
847 json.svg = data['image/svg+xml'];
848 };
848 };
849 if (data['image/png'] !== undefined) {
849 if (data['image/png'] !== undefined) {
850 json.png = data['image/png'];
850 json.png = data['image/png'];
851 };
851 };
852 if (data['image/jpeg'] !== undefined) {
852 if (data['image/jpeg'] !== undefined) {
853 json.jpeg = data['image/jpeg'];
853 json.jpeg = data['image/jpeg'];
854 };
854 };
855 if (data['text/latex'] !== undefined) {
855 if (data['text/latex'] !== undefined) {
856 json.latex = data['text/latex'];
856 json.latex = data['text/latex'];
857 };
857 };
858 if (data['application/json'] !== undefined) {
858 if (data['application/json'] !== undefined) {
859 json.json = data['application/json'];
859 json.json = data['application/json'];
860 };
860 };
861 if (data['application/javascript'] !== undefined) {
861 if (data['application/javascript'] !== undefined) {
862 json.javascript = data['application/javascript'];
862 json.javascript = data['application/javascript'];
863 }
863 }
864 return json;
864 return json;
865 };
865 };
866
866
867
867
868 Notebook.prototype.execute_selected_cell = function (options) {
868 Notebook.prototype.execute_selected_cell = function (options) {
869 // add_new: should a new cell be added if we are at the end of the nb
869 // add_new: should a new cell be added if we are at the end of the nb
870 // terminal: execute in terminal mode, which stays in the current cell
870 // terminal: execute in terminal mode, which stays in the current cell
871 default_options = {terminal: false, add_new: true};
871 default_options = {terminal: false, add_new: true};
872 $.extend(default_options, options);
872 $.extend(default_options, options);
873 var that = this;
873 var that = this;
874 var cell = that.selected_cell();
874 var cell = that.selected_cell();
875 var cell_index = that.find_cell_index(cell);
875 var cell_index = that.find_cell_index(cell);
876 if (cell instanceof IPython.CodeCell) {
876 if (cell instanceof IPython.CodeCell) {
877 cell.clear_output(true, true, true);
877 cell.clear_output(true, true, true);
878 cell.set_input_prompt('*');
878 cell.set_input_prompt('*');
879 cell.element.addClass("running");
879 cell.element.addClass("running");
880 var code = cell.get_code();
880 var code = cell.get_code();
881 var msg_id = that.kernel.execute(cell.get_code());
881 var msg_id = that.kernel.execute(cell.get_code());
882 that.msg_cell_map[msg_id] = cell.cell_id;
882 that.msg_cell_map[msg_id] = cell.cell_id;
883 } else if (cell instanceof IPython.HTMLCell) {
883 } else if (cell instanceof IPython.HTMLCell) {
884 cell.render();
884 cell.render();
885 }
885 }
886 if (default_options.terminal) {
886 if (default_options.terminal) {
887 cell.select_all();
887 cell.select_all();
888 } else {
888 } else {
889 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
889 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
890 that.insert_code_cell_below();
890 that.insert_code_cell_below();
891 // If we are adding a new cell at the end, scroll down to show it.
891 // If we are adding a new cell at the end, scroll down to show it.
892 that.scroll_to_bottom();
892 that.scroll_to_bottom();
893 } else {
893 } else {
894 that.select(cell_index+1);
894 that.select(cell_index+1);
895 };
895 };
896 };
896 };
897 this.dirty = true;
897 this.dirty = true;
898 };
898 };
899
899
900
900
901 Notebook.prototype.execute_all_cells = function () {
901 Notebook.prototype.execute_all_cells = function () {
902 var ncells = this.ncells();
902 var ncells = this.ncells();
903 for (var i=0; i<ncells; i++) {
903 for (var i=0; i<ncells; i++) {
904 this.select(i);
904 this.select(i);
905 this.execute_selected_cell({add_new:false});
905 this.execute_selected_cell({add_new:false});
906 };
906 };
907 this.scroll_to_bottom();
907 this.scroll_to_bottom();
908 };
908 };
909
909
910
910
911 Notebook.prototype.request_tool_tip = function (cell,func) {
911 Notebook.prototype.request_tool_tip = function (cell,func) {
912 // Feel free to shorten this logic if you are better
912 // Feel free to shorten this logic if you are better
913 // than me in regEx
913 // than me in regEx
914 // basicaly you shoul be able to get xxx.xxx.xxx from
914 // basicaly you shoul be able to get xxx.xxx.xxx from
915 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
915 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
916 // remove everything between matchin bracket (need to iterate)
916 // remove everything between matchin bracket (need to iterate)
917 matchBracket = /\([^\(\)]+\)/g;
917 matchBracket = /\([^\(\)]+\)/g;
918 oldfunc = func;
918 oldfunc = func;
919 func = func.replace(matchBracket,"");
919 func = func.replace(matchBracket,"");
920 while( oldfunc != func )
920 while( oldfunc != func )
921 {
921 {
922 oldfunc = func;
922 oldfunc = func;
923 func = func.replace(matchBracket,"");
923 func = func.replace(matchBracket,"");
924 }
924 }
925 // remove everythin after last open bracket
925 // remove everythin after last open bracket
926 endBracket = /\([^\(]*$/g;
926 endBracket = /\([^\(]*$/g;
927 func = func.replace(endBracket,"");
927 func = func.replace(endBracket,"");
928 var re = /[a-zA-Z._]+$/g;
928 var re = /[a-zA-Z._]+$/g;
929 var msg_id = this.kernel.object_info_request(re.exec(func));
929 var msg_id = this.kernel.object_info_request(re.exec(func));
930 if(typeof(msg_id)!='undefined'){
930 if(typeof(msg_id)!='undefined'){
931 this.msg_cell_map[msg_id] = cell.cell_id;
931 this.msg_cell_map[msg_id] = cell.cell_id;
932 }
932 }
933 };
933 };
934
934
935 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
935 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
936 var msg_id = this.kernel.complete(line, cursor_pos);
936 var msg_id = this.kernel.complete(line, cursor_pos);
937 this.msg_cell_map[msg_id] = cell.cell_id;
937 this.msg_cell_map[msg_id] = cell.cell_id;
938 };
938 };
939
939
940 // Persistance and loading
940 // Persistance and loading
941
941
942
942
943 Notebook.prototype.fromJSON = function (data) {
943 Notebook.prototype.fromJSON = function (data) {
944 var ncells = this.ncells();
944 var ncells = this.ncells();
945 var i;
945 var i;
946 for (i=0; i<ncells; i++) {
946 for (i=0; i<ncells; i++) {
947 // Always delete cell 0 as they get renumbered as they are deleted.
947 // Always delete cell 0 as they get renumbered as they are deleted.
948 this.delete_cell(0);
948 this.delete_cell(0);
949 };
949 };
950 // Save the metadata
950 // Save the metadata
951 this.metadata = data.metadata;
951 this.metadata = data.metadata;
952 // Only handle 1 worksheet for now.
952 // Only handle 1 worksheet for now.
953 var worksheet = data.worksheets[0];
953 var worksheet = data.worksheets[0];
954 if (worksheet !== undefined) {
954 if (worksheet !== undefined) {
955 var new_cells = worksheet.cells;
955 var new_cells = worksheet.cells;
956 ncells = new_cells.length;
956 ncells = new_cells.length;
957 var cell_data = null;
957 var cell_data = null;
958 var new_cell = null;
958 var new_cell = null;
959 for (i=0; i<ncells; i++) {
959 for (i=0; i<ncells; i++) {
960 cell_data = new_cells[i];
960 cell_data = new_cells[i];
961 if (cell_data.cell_type == 'code') {
961 if (cell_data.cell_type == 'code') {
962 new_cell = this.insert_code_cell_below();
962 new_cell = this.insert_code_cell_below();
963 new_cell.fromJSON(cell_data);
963 new_cell.fromJSON(cell_data);
964 } else if (cell_data.cell_type === 'html') {
964 } else if (cell_data.cell_type === 'html') {
965 new_cell = this.insert_html_cell_below();
965 new_cell = this.insert_html_cell_below();
966 new_cell.fromJSON(cell_data);
966 new_cell.fromJSON(cell_data);
967 } else if (cell_data.cell_type === 'markdown') {
967 } else if (cell_data.cell_type === 'markdown') {
968 new_cell = this.insert_markdown_cell_below();
968 new_cell = this.insert_markdown_cell_below();
969 new_cell.fromJSON(cell_data);
969 new_cell.fromJSON(cell_data);
970 };
970 };
971 };
971 };
972 };
972 };
973 };
973 };
974
974
975
975
976 Notebook.prototype.toJSON = function () {
976 Notebook.prototype.toJSON = function () {
977 var cells = this.cells();
977 var cells = this.cells();
978 var ncells = cells.length;
978 var ncells = cells.length;
979 cell_array = new Array(ncells);
979 cell_array = new Array(ncells);
980 for (var i=0; i<ncells; i++) {
980 for (var i=0; i<ncells; i++) {
981 cell_array[i] = cells[i].toJSON();
981 cell_array[i] = cells[i].toJSON();
982 };
982 };
983 data = {
983 data = {
984 // Only handle 1 worksheet for now.
984 // Only handle 1 worksheet for now.
985 worksheets : [{cells:cell_array}],
985 worksheets : [{cells:cell_array}],
986 metadata : this.metadata
986 metadata : this.metadata
987 };
987 };
988 return data;
988 return data;
989 };
989 };
990
990
991 Notebook.prototype.save_notebook = function () {
991 Notebook.prototype.save_notebook = function () {
992 if (IPython.save_widget.test_notebook_name()) {
992 if (IPython.save_widget.test_notebook_name()) {
993 var notebook_id = IPython.save_widget.get_notebook_id();
993 var notebook_id = IPython.save_widget.get_notebook_id();
994 var nbname = IPython.save_widget.get_notebook_name();
994 var nbname = IPython.save_widget.get_notebook_name();
995 // We may want to move the name/id/nbformat logic inside toJSON?
995 // We may want to move the name/id/nbformat logic inside toJSON?
996 var data = this.toJSON();
996 var data = this.toJSON();
997 data.metadata.name = nbname;
997 data.metadata.name = nbname;
998 data.nbformat = 2;
998 data.nbformat = 2;
999 // We do the call with settings so we can set cache to false.
999 // We do the call with settings so we can set cache to false.
1000 var settings = {
1000 var settings = {
1001 processData : false,
1001 processData : false,
1002 cache : false,
1002 cache : false,
1003 type : "PUT",
1003 type : "PUT",
1004 data : JSON.stringify(data),
1004 data : JSON.stringify(data),
1005 headers : {'Content-Type': 'application/json'},
1005 headers : {'Content-Type': 'application/json'},
1006 success : $.proxy(this.notebook_saved,this),
1006 success : $.proxy(this.notebook_saved,this),
1007 error : $.proxy(this.notebook_save_failed,this)
1007 error : $.proxy(this.notebook_save_failed,this)
1008 };
1008 };
1009 IPython.save_widget.status_saving();
1009 IPython.save_widget.status_saving();
1010 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
1010 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
1011 $.ajax(url, settings);
1011 $.ajax(url, settings);
1012 };
1012 };
1013 };
1013 };
1014
1014
1015
1015
1016 Notebook.prototype.notebook_saved = function (data, status, xhr) {
1016 Notebook.prototype.notebook_saved = function (data, status, xhr) {
1017 this.dirty = false;
1017 this.dirty = false;
1018 IPython.save_widget.notebook_saved();
1018 IPython.save_widget.notebook_saved();
1019 IPython.save_widget.status_save();
1019 IPython.save_widget.status_save();
1020 };
1020 };
1021
1021
1022
1022
1023 Notebook.prototype.notebook_save_failed = function (xhr, status, error_msg) {
1023 Notebook.prototype.notebook_save_failed = function (xhr, status, error_msg) {
1024 // Notify the user and reset the save button
1024 // Notify the user and reset the save button
1025 // TODO: Handle different types of errors (timeout etc.)
1025 // TODO: Handle different types of errors (timeout etc.)
1026 alert('An unexpected error occured while saving the notebook.');
1026 alert('An unexpected error occured while saving the notebook.');
1027 IPython.save_widget.reset_status();
1027 IPython.save_widget.reset_status();
1028 };
1028 };
1029
1029
1030
1030
1031 Notebook.prototype.load_notebook = function (callback) {
1031 Notebook.prototype.load_notebook = function (callback) {
1032 var that = this;
1032 var that = this;
1033 var notebook_id = IPython.save_widget.get_notebook_id();
1033 var notebook_id = IPython.save_widget.get_notebook_id();
1034 // We do the call with settings so we can set cache to false.
1034 // We do the call with settings so we can set cache to false.
1035 var settings = {
1035 var settings = {
1036 processData : false,
1036 processData : false,
1037 cache : false,
1037 cache : false,
1038 type : "GET",
1038 type : "GET",
1039 dataType : "json",
1039 dataType : "json",
1040 success : function (data, status, xhr) {
1040 success : function (data, status, xhr) {
1041 that.notebook_loaded(data, status, xhr);
1041 that.notebook_loaded(data, status, xhr);
1042 if (callback !== undefined) {
1042 if (callback !== undefined) {
1043 callback();
1043 callback();
1044 };
1044 };
1045 }
1045 }
1046 };
1046 };
1047 IPython.save_widget.status_loading();
1047 IPython.save_widget.status_loading();
1048 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
1048 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
1049 $.ajax(url, settings);
1049 $.ajax(url, settings);
1050 };
1050 };
1051
1051
1052
1052
1053 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
1053 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
1054 var allowed = xhr.getResponseHeader('Allow');
1054 var allowed = xhr.getResponseHeader('Allow');
1055 this.fromJSON(data);
1055 this.fromJSON(data);
1056 if (this.ncells() === 0) {
1056 if (this.ncells() === 0) {
1057 this.insert_code_cell_below();
1057 this.insert_code_cell_below();
1058 };
1058 };
1059 IPython.save_widget.status_save();
1059 IPython.save_widget.status_save();
1060 IPython.save_widget.set_notebook_name(data.metadata.name);
1060 IPython.save_widget.set_notebook_name(data.metadata.name);
1061 this.dirty = false;
1061 this.dirty = false;
1062 if (! this.read_only) {
1062 if (! this.read_only) {
1063 this.start_kernel();
1063 this.start_kernel();
1064 }
1064 }
1065 // fromJSON always selects the last cell inserted. We need to wait
1065 // fromJSON always selects the last cell inserted. We need to wait
1066 // until that is done before scrolling to the top.
1066 // until that is done before scrolling to the top.
1067 setTimeout(function () {
1067 setTimeout(function () {
1068 IPython.notebook.select(0);
1068 IPython.notebook.select(0);
1069 IPython.notebook.scroll_to_top();
1069 IPython.notebook.scroll_to_top();
1070 }, 50);
1070 }, 50);
1071 };
1071 };
1072
1072
1073 IPython.Notebook = Notebook;
1073 IPython.Notebook = Notebook;
1074
1074
1075
1075
1076 return IPython;
1076 return IPython;
1077
1077
1078 }(IPython));
1078 }(IPython));
1079
1079
@@ -1,85 +1,135 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 if (window.MathJax){
15 // MathJax loaded
15 MathJax.Hub.Config({
16 MathJax.Hub.Config({
16 tex2jax: {
17 tex2jax: {
17 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
18 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
18 displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
19 displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
19 },
20 },
20 displayAlign: 'left', // Change this to 'center' to center equations.
21 displayAlign: 'left', // Change this to 'center' to center equations.
21 "HTML-CSS": {
22 "HTML-CSS": {
22 styles: {'.MathJax_Display': {"margin": 0}}
23 styles: {'.MathJax_Display': {"margin": 0}}
23 }
24 }
24 });
25 });
26 }else if (window.mathjax_url != ""){
27 // Don't have MathJax, but should. Show dialog.
28 var dialog = $('<div></div>')
29 .append(
30 $("<p></p>").addClass('dialog').html(
31 "Math/LaTeX rendering will be disabled."
32 )
33 ).append(
34 $("<p></p>").addClass('dialog').html(
35 "If you have administrative access to the notebook server and" +
36 " a working internet connection, you can install a local copy" +
37 " of MathJax for offline use with the following command on the server" +
38 " at a Python or IPython prompt:"
39 )
40 ).append(
41 $("<pre></pre>").addClass('dialog').html(
42 ">>> from IPython.external import mathjax; mathjax.install_mathjax()"
43 )
44 ).append(
45 $("<p></p>").addClass('dialog').html(
46 "This will try to install MathJax into the IPython source directory."
47 )
48 ).append(
49 $("<p></p>").addClass('dialog').html(
50 "If IPython is installed to a location that requires" +
51 " administrative privileges to write, you will need to make this call as" +
52 " an administrator, via 'sudo'."
53 )
54 ).append(
55 $("<p></p>").addClass('dialog').html(
56 "When you start the notebook server, you can instruct it to disable MathJax support altogether:"
57 )
58 ).append(
59 $("<pre></pre>").addClass('dialog').html(
60 "$ ipython notebook --no-mathjax"
61 )
62 ).append(
63 $("<p></p>").addClass('dialog').html(
64 "which will prevent this dialog from appearing."
65 )
66 ).dialog({
67 title: "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
68 width: "70%",
69 modal: true,
70 })
71 }else{
72 // No MathJax, but none expected. No dialog.
73 }
74
25 IPython.markdown_converter = new Markdown.Converter();
75 IPython.markdown_converter = new Markdown.Converter();
26 IPython.read_only = $('meta[name=read_only]').attr("content") == 'True';
76 IPython.read_only = $('meta[name=read_only]').attr("content") == 'True';
27
77
28 $('div#header').addClass('border-box-sizing');
78 $('div#header').addClass('border-box-sizing');
29 $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content');
79 $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content');
30 $('div#notebook_panel').addClass('border-box-sizing ui-widget');
80 $('div#notebook_panel').addClass('border-box-sizing ui-widget');
31
81
32 IPython.layout_manager = new IPython.LayoutManager();
82 IPython.layout_manager = new IPython.LayoutManager();
33 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
83 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
34 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
84 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
35 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
85 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
36 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
86 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
37 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
87 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
38 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
88 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
39 IPython.notebook = new IPython.Notebook('div#notebook');
89 IPython.notebook = new IPython.Notebook('div#notebook');
40 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
90 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
41 IPython.kernel_status_widget.status_idle();
91 IPython.kernel_status_widget.status_idle();
42
92
43 IPython.layout_manager.do_resize();
93 IPython.layout_manager.do_resize();
44
94
45 // These have display: none in the css file and are made visible here to prevent FLOUC.
95 // These have display: none in the css file and are made visible here to prevent FLOUC.
46 $('div#header').css('display','block');
96 $('div#header').css('display','block');
47
97
48 if(IPython.read_only){
98 if(IPython.read_only){
49 // hide various elements from read-only view
99 // hide various elements from read-only view
50 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
100 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
51 IPython.quick_help.element.addClass('hidden'); // shortcuts are disabled in read_only
101 IPython.quick_help.element.addClass('hidden'); // shortcuts are disabled in read_only
52 $('button#new_notebook').addClass('hidden');
102 $('button#new_notebook').addClass('hidden');
53 $('div#cell_section').addClass('hidden');
103 $('div#cell_section').addClass('hidden');
54 $('div#config_section').addClass('hidden');
104 $('div#config_section').addClass('hidden');
55 $('div#kernel_section').addClass('hidden');
105 $('div#kernel_section').addClass('hidden');
56 $('span#login_widget').removeClass('hidden');
106 $('span#login_widget').removeClass('hidden');
57 // left panel starts collapsed, but the collapse must happen after
107 // left panel starts collapsed, but the collapse must happen after
58 // elements start drawing. Don't draw contents of the panel until
108 // elements start drawing. Don't draw contents of the panel until
59 // after they are collapsed
109 // after they are collapsed
60 IPython.left_panel.left_panel_element.css('visibility', 'hidden');
110 IPython.left_panel.left_panel_element.css('visibility', 'hidden');
61 }
111 }
62
112
63 $('div#main_app').css('display','block');
113 $('div#main_app').css('display','block');
64
114
65 // Perform these actions after the notebook has been loaded.
115 // Perform these actions after the notebook has been loaded.
66 // We wait 100 milliseconds because the notebook scrolls to the top after a load
116 // 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.
117 // is completed and we need to wait for that to mostly finish.
68 IPython.notebook.load_notebook(function () {
118 IPython.notebook.load_notebook(function () {
69 setTimeout(function () {
119 setTimeout(function () {
70 IPython.save_widget.update_url();
120 IPython.save_widget.update_url();
71 IPython.layout_manager.do_resize();
121 IPython.layout_manager.do_resize();
72 IPython.pager.collapse();
122 IPython.pager.collapse();
73 if(IPython.read_only){
123 if(IPython.read_only){
74 // collapse the left panel on read-only
124 // collapse the left panel on read-only
75 IPython.left_panel.collapse();
125 IPython.left_panel.collapse();
76 // and finally unhide the panel contents after collapse
126 // and finally unhide the panel contents after collapse
77 setTimeout(function(){
127 setTimeout(function(){
78 IPython.left_panel.left_panel_element.css('visibility', 'visible');
128 IPython.left_panel.left_panel_element.css('visibility', 'visible');
79 }, 200);
129 }, 200);
80 }
130 }
81 },100);
131 },100);
82 });
132 });
83
133
84 });
134 });
85
135
@@ -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,328 +1,294 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 <!-- <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" charset="utf-8"></script> -->
9 {% if mathjax_url %}
10 <script type='text/javascript' src='static/mathjax/MathJax.js?config=TeX-AMS_HTML' charset='utf-8'></script>
10 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML" charset="utf-8"></script>
11 {% end %}
11 <script type="text/javascript">
12 <script type="text/javascript">
12 function CheckMathJax(){
13 // MathJax disabled, set as null to distingish from *missing* MathJax,
13 var div=document.getElementById("MathJaxFetchingWarning")
14 // where it will be undefined, and should prompt a dialog later.
14 if(window.MathJax){
15 window.mathjax_url = "{{mathjax_url}}";
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");
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"));
24 }else{
25 console.log("Using local MathJax");
26 }
27 </script>
16 </script>
28
17
29 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
18 <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">
19 <link rel="stylesheet" href="static/codemirror/lib/codemirror.css">
31 <link rel="stylesheet" href="static/codemirror/mode/markdown/markdown.css">
20 <link rel="stylesheet" href="static/codemirror/mode/markdown/markdown.css">
32 <link rel="stylesheet" href="static/codemirror/mode/rst/rst.css">
21 <link rel="stylesheet" href="static/codemirror/mode/rst/rst.css">
33 <link rel="stylesheet" href="static/codemirror/theme/ipython.css">
22 <link rel="stylesheet" href="static/codemirror/theme/ipython.css">
34 <link rel="stylesheet" href="static/codemirror/theme/default.css">
23 <link rel="stylesheet" href="static/codemirror/theme/default.css">
35
24
36 <link rel="stylesheet" href="static/prettify/prettify.css"/>
25 <link rel="stylesheet" href="static/prettify/prettify.css"/>
37
26
38 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
27 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
39 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
28 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
40 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
29 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
41 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
30 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
42 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
31 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
43
32
44 <meta name="read_only" content="{{read_only}}"/>
33 <meta name="read_only" content="{{read_only}}"/>
45
34
46 </head>
35 </head>
47
36
48 <body onload='CheckMathJax();'
37 <body
49 data-project={{project}} data-notebook-id={{notebook_id}}
38 data-project={{project}} data-notebook-id={{notebook_id}}
50 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
39 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
51 >
40 >
52
41
53 <div id="header">
42 <div id="header">
54 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
43 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
55 <span id="save_widget">
44 <span id="save_widget">
56 <input type="text" id="notebook_name" size="20"></textarea>
45 <input type="text" id="notebook_name" size="20"></textarea>
57 <button id="save_notebook"><u>S</u>ave</button>
46 <button id="save_notebook"><u>S</u>ave</button>
58 </span>
47 </span>
59 <span id="quick_help_area">
48 <span id="quick_help_area">
60 <button id="quick_help">Quick<u>H</u>elp</button>
49 <button id="quick_help">Quick<u>H</u>elp</button>
61 </span>
50 </span>
62
51
63 <span id="login_widget">
52 <span id="login_widget">
64 {% comment This is a temporary workaround to hide the logout button %}
53 {% comment This is a temporary workaround to hide the logout button %}
65 {% comment when appropriate until notebook.html is templated %}
54 {% comment when appropriate until notebook.html is templated %}
66 {% if current_user and current_user != 'anonymous' %}
55 {% if current_user and current_user != 'anonymous' %}
67 <button id="logout">Logout</button>
56 <button id="logout">Logout</button>
68 {% end %}
57 {% end %}
69 </span>
58 </span>
70
59
71 <span id="kernel_status">Idle</span>
60 <span id="kernel_status">Idle</span>
72 </div>
61 </div>
73
62
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">
63 <div id="main_app">
98
64
99 <div id="left_panel">
65 <div id="left_panel">
100
66
101 <div id="notebook_section">
67 <div id="notebook_section">
102 <div class="section_header">
68 <div class="section_header">
103 <h3>Notebook</h3>
69 <h3>Notebook</h3>
104 </div>
70 </div>
105 <div class="section_content">
71 <div class="section_content">
106 <div class="section_row">
72 <div class="section_row">
107 <span id="new_open" class="section_row_buttons">
73 <span id="new_open" class="section_row_buttons">
108 <button id="new_notebook">New</button>
74 <button id="new_notebook">New</button>
109 <button id="open_notebook">Open</button>
75 <button id="open_notebook">Open</button>
110 </span>
76 </span>
111 <span class="section_row_header">Actions</span>
77 <span class="section_row_header">Actions</span>
112 </div>
78 </div>
113 <div class="section_row">
79 <div class="section_row">
114 <span>
80 <span>
115 <select id="download_format">
81 <select id="download_format">
116 <option value="json">ipynb</option>
82 <option value="json">ipynb</option>
117 <option value="py">py</option>
83 <option value="py">py</option>
118 </select>
84 </select>
119 </span>
85 </span>
120 <span class="section_row_buttons">
86 <span class="section_row_buttons">
121 <button id="download_notebook">Download</button>
87 <button id="download_notebook">Download</button>
122 </span>
88 </span>
123 </div>
89 </div>
124 <div class="section_row">
90 <div class="section_row">
125 <span class="section_row_buttons">
91 <span class="section_row_buttons">
126 <span id="print_widget">
92 <span id="print_widget">
127 <button id="print_notebook">Print</button>
93 <button id="print_notebook">Print</button>
128 </span>
94 </span>
129 </span>
95 </span>
130 </div>
96 </div>
131 </div>
97 </div>
132 </div>
98 </div>
133
99
134 <div id="cell_section">
100 <div id="cell_section">
135 <div class="section_header">
101 <div class="section_header">
136 <h3>Cell</h3>
102 <h3>Cell</h3>
137 </div>
103 </div>
138 <div class="section_content">
104 <div class="section_content">
139 <div class="section_row">
105 <div class="section_row">
140 <span class="section_row_buttons">
106 <span class="section_row_buttons">
141 <button id="delete_cell"><u>D</u>elete</button>
107 <button id="delete_cell"><u>D</u>elete</button>
142 </span>
108 </span>
143 <span class="section_row_header">Actions</span>
109 <span class="section_row_header">Actions</span>
144 </div>
110 </div>
145 <div class="section_row">
111 <div class="section_row">
146 <span id="cell_type" class="section_row_buttons">
112 <span id="cell_type" class="section_row_buttons">
147 <button id="to_code"><u>C</u>ode</button>
113 <button id="to_code"><u>C</u>ode</button>
148 <!-- <button id="to_html">HTML</button>-->
114 <!-- <button id="to_html">HTML</button>-->
149 <button id="to_markdown"><u>M</u>arkdown</button>
115 <button id="to_markdown"><u>M</u>arkdown</button>
150 </span>
116 </span>
151 <span class="button_label">Format</span>
117 <span class="button_label">Format</span>
152 </div>
118 </div>
153 <div class="section_row">
119 <div class="section_row">
154 <span id="cell_output" class="section_row_buttons">
120 <span id="cell_output" class="section_row_buttons">
155 <button id="toggle_output"><u>T</u>oggle</button>
121 <button id="toggle_output"><u>T</u>oggle</button>
156 <button id="clear_all_output">ClearAll</button>
122 <button id="clear_all_output">ClearAll</button>
157 </span>
123 </span>
158 <span class="button_label">Output</span>
124 <span class="button_label">Output</span>
159 </div>
125 </div>
160 <div class="section_row">
126 <div class="section_row">
161 <span id="insert" class="section_row_buttons">
127 <span id="insert" class="section_row_buttons">
162 <button id="insert_cell_above"><u>A</u>bove</button>
128 <button id="insert_cell_above"><u>A</u>bove</button>
163 <button id="insert_cell_below"><u>B</u>elow</button>
129 <button id="insert_cell_below"><u>B</u>elow</button>
164 </span>
130 </span>
165 <span class="button_label">Insert</span>
131 <span class="button_label">Insert</span>
166 </div>
132 </div>
167 <div class="section_row">
133 <div class="section_row">
168 <span id="move" class="section_row_buttons">
134 <span id="move" class="section_row_buttons">
169 <button id="move_cell_up">Up</button>
135 <button id="move_cell_up">Up</button>
170 <button id="move_cell_down">Down</button>
136 <button id="move_cell_down">Down</button>
171 </span>
137 </span>
172 <span class="button_label">Move</span>
138 <span class="button_label">Move</span>
173 </div>
139 </div>
174 <div class="section_row">
140 <div class="section_row">
175 <span id="run_cells" class="section_row_buttons">
141 <span id="run_cells" class="section_row_buttons">
176 <button id="run_selected_cell">Selected</button>
142 <button id="run_selected_cell">Selected</button>
177 <button id="run_all_cells">All</button>
143 <button id="run_all_cells">All</button>
178 </span>
144 </span>
179 <span class="button_label">Run</span>
145 <span class="button_label">Run</span>
180 </div>
146 </div>
181 <div class="section_row">
147 <div class="section_row">
182 <span id="autoindent_span">
148 <span id="autoindent_span">
183 <input type="checkbox" id="autoindent" checked="true"></input>
149 <input type="checkbox" id="autoindent" checked="true"></input>
184 </span>
150 </span>
185 <span class="checkbox_label" id="autoindent_label">Autoindent:</span>
151 <span class="checkbox_label" id="autoindent_label">Autoindent:</span>
186 </div>
152 </div>
187 </div>
153 </div>
188 </div>
154 </div>
189
155
190 <div id="kernel_section">
156 <div id="kernel_section">
191 <div class="section_header">
157 <div class="section_header">
192 <h3>Kernel</h3>
158 <h3>Kernel</h3>
193 </div>
159 </div>
194 <div class="section_content">
160 <div class="section_content">
195 <div class="section_row">
161 <div class="section_row">
196 <span id="int_restart" class="section_row_buttons">
162 <span id="int_restart" class="section_row_buttons">
197 <button id="int_kernel"><u>I</u>nterrupt</button>
163 <button id="int_kernel"><u>I</u>nterrupt</button>
198 <button id="restart_kernel">Restart</button>
164 <button id="restart_kernel">Restart</button>
199 </span>
165 </span>
200 <span class="section_row_header">Actions</span>
166 <span class="section_row_header">Actions</span>
201 </div>
167 </div>
202 <div class="section_row">
168 <div class="section_row">
203 <span id="kernel_persist">
169 <span id="kernel_persist">
204 {% if kill_kernel %}
170 {% if kill_kernel %}
205 <input type="checkbox" id="kill_kernel" checked="true"></input>
171 <input type="checkbox" id="kill_kernel" checked="true"></input>
206 {% else %}
172 {% else %}
207 <input type="checkbox" id="kill_kernel"></input>
173 <input type="checkbox" id="kill_kernel"></input>
208 {% end %}
174 {% end %}
209 </span>
175 </span>
210 <span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
176 <span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
211 </div>
177 </div>
212 </div>
178 </div>
213 </div>
179 </div>
214
180
215 <div id="help_section">
181 <div id="help_section">
216 <div class="section_header">
182 <div class="section_header">
217 <h3>Help</h3>
183 <h3>Help</h3>
218 </div>
184 </div>
219 <div class="section_content">
185 <div class="section_content">
220 <div class="section_row">
186 <div class="section_row">
221 <span id="help_buttons0" class="section_row_buttons">
187 <span id="help_buttons0" class="section_row_buttons">
222 <a id="python_help" href="http://docs.python.org" target="_blank">Python</a>
188 <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>
189 <a id="ipython_help" href="http://ipython.org/documentation.html" target="_blank">IPython</a>
224 </span>
190 </span>
225 <span class="section_row_header">Links</span>
191 <span class="section_row_header">Links</span>
226 </div>
192 </div>
227 <div class="section_row">
193 <div class="section_row">
228 <span id="help_buttons1" class="section_row_buttons">
194 <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>
195 <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>
196 <a id="scipy_help" href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a>
231 </span>
197 </span>
232 </div>
198 </div>
233 <div class="section_row">
199 <div class="section_row">
234 <span id="help_buttons2" class="section_row_buttons">
200 <span id="help_buttons2" class="section_row_buttons">
235 <a id="matplotlib_help" href="http://matplotlib.sourceforge.net/" target="_blank">MPL</a>
201 <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>
202 <a id="sympy_help" href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a>
237 </span>
203 </span>
238 </div>
204 </div>
239 <div class="section_row">
205 <div class="section_row">
240 <span class="help_string">run selected cell</span>
206 <span class="help_string">run selected cell</span>
241 <span class="help_string_label">Shift-Enter :</span>
207 <span class="help_string_label">Shift-Enter :</span>
242 </div>
208 </div>
243 <div class="section_row">
209 <div class="section_row">
244 <span class="help_string">run selected cell in-place</span>
210 <span class="help_string">run selected cell in-place</span>
245 <span class="help_string_label">Ctrl-Enter :</span>
211 <span class="help_string_label">Ctrl-Enter :</span>
246 </div>
212 </div>
247 <div class="section_row">
213 <div class="section_row">
248 <span class="help_string">show keyboard shortcuts</span>
214 <span class="help_string">show keyboard shortcuts</span>
249 <span class="help_string_label">Ctrl-m h :</span>
215 <span class="help_string_label">Ctrl-m h :</span>
250 </div>
216 </div>
251 </div>
217 </div>
252 </div>
218 </div>
253
219
254 <div id="config_section">
220 <div id="config_section">
255 <div class="section_header">
221 <div class="section_header">
256 <h3>Configuration</h3>
222 <h3>Configuration</h3>
257 </div>
223 </div>
258 <div class="section_content">
224 <div class="section_content">
259 <div class="section_row">
225 <div class="section_row">
260 <span id="tooltipontab_span">
226 <span id="tooltipontab_span">
261 <input type="checkbox" id="tooltipontab" checked="true"></input>
227 <input type="checkbox" id="tooltipontab" checked="true"></input>
262 </span>
228 </span>
263 <span class="checkbox_label" id="tooltipontab_label">Tooltip on tab:</span>
229 <span class="checkbox_label" id="tooltipontab_label">Tooltip on tab:</span>
264 </div>
230 </div>
265 <div class="section_row">
231 <div class="section_row">
266 <span id="smartcompleter_span">
232 <span id="smartcompleter_span">
267 <input type="checkbox" id="smartcompleter" checked="true"></input>
233 <input type="checkbox" id="smartcompleter" checked="true"></input>
268 </span>
234 </span>
269 <span class="checkbox_label" id="smartcompleter_label">Smart completer:</span>
235 <span class="checkbox_label" id="smartcompleter_label">Smart completer:</span>
270 </div>
236 </div>
271 <div class="section_row">
237 <div class="section_row">
272 <span id="timebeforetooltip_span">
238 <span id="timebeforetooltip_span">
273 <input type="text" id="timebeforetooltip" value="1200" size='6'></input>
239 <input type="text" id="timebeforetooltip" value="1200" size='6'></input>
274 <span class="numeric_input_label" id="timebeforetooltip_unit">milliseconds</span>
240 <span class="numeric_input_label" id="timebeforetooltip_unit">milliseconds</span>
275 </span>
241 </span>
276 <span class="numeric_input_label" id="timebeforetooltip_label">Time before tooltip : </span>
242 <span class="numeric_input_label" id="timebeforetooltip_label">Time before tooltip : </span>
277 </div>
243 </div>
278 </div>
244 </div>
279 </div>
245 </div>
280
246
281 </div>
247 </div>
282 <div id="left_panel_splitter"></div>
248 <div id="left_panel_splitter"></div>
283 <div id="notebook_panel">
249 <div id="notebook_panel">
284 <div id="notebook"></div>
250 <div id="notebook"></div>
285 <div id="pager_splitter"></div>
251 <div id="pager_splitter"></div>
286 <div id="pager"></div>
252 <div id="pager"></div>
287 </div>
253 </div>
288
254
289 </div>
255 </div>
290
256
291 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
257 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
292 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
258 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
293 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
259 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
294
260
295 <script src="static/codemirror/lib/codemirror.js" charset="utf-8"></script>
261 <script src="static/codemirror/lib/codemirror.js" charset="utf-8"></script>
296 <script src="static/codemirror/mode/python/python.js" charset="utf-8"></script>
262 <script src="static/codemirror/mode/python/python.js" charset="utf-8"></script>
297 <script src="static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
263 <script src="static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
298 <script src="static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
264 <script src="static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
299 <script src="static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
265 <script src="static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
300 <script src="static/codemirror/mode/css/css.js" charset="utf-8"></script>
266 <script src="static/codemirror/mode/css/css.js" charset="utf-8"></script>
301 <script src="static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
267 <script src="static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
302 <script src="static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
268 <script src="static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
303
269
304 <script src="static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
270 <script src="static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
305
271
306 <script src="static/prettify/prettify.js" charset="utf-8"></script>
272 <script src="static/prettify/prettify.js" charset="utf-8"></script>
307
273
308 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
274 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
309 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
275 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
310 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
276 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
311 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
277 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
312 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
278 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
313 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
279 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
314 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
280 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
315 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
281 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
316 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
282 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
317 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
283 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
318 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
284 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
319 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
285 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
320 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
286 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
321 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
287 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
322 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
288 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
323 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
289 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
324 <script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
290 <script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
325
291
326 </body>
292 </body>
327
293
328 </html>
294 </html>
General Comments 0
You need to be logged in to leave comments. Login now