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