##// END OF EJS Templates
allow the notebook to run without MathJax...
MinRK -
Show More
@@ -1,579 +1,581
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 enable_mathjax=self.application.ipython_app.enable_mathjax,
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 enable_mathjax=self.application.ipython_app.enable_mathjax,
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,360
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 )
233 244
245 enable_mathjax = Bool(True, config=True,
246 help="""Whether to enable MathJax for typesetting math/TeX
247
248 MathJax is the javascript library IPython uses to render math/LaTeX. It is
249 very large, so you may want to disable it if you have a slow internet
250 connection, or for offline use of the notebook.
251
252 When disabled, equations etc. will appear as their untransformed TeX source.
253 """
254 )
255
234 256 def parse_command_line(self, argv=None):
235 257 super(NotebookApp, self).parse_command_line(argv)
236 258 if argv is None:
237 259 argv = sys.argv[1:]
238 260
239 261 self.kernel_argv = list(argv) # copy
240 262 # Kernel should inherit default config file from frontend
241 263 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
242 264 # Scrub frontend-specific flags
243 265 for a in argv:
244 266 if a.startswith('-') and a.lstrip('-') in notebook_flags:
245 267 self.kernel_argv.remove(a)
246 268 swallow_next = False
247 269 for a in argv:
248 270 if swallow_next:
249 271 self.kernel_argv.remove(a)
250 272 swallow_next = False
251 273 continue
252 274 if a.startswith('-'):
253 275 split = a.lstrip('-').split('=')
254 276 alias = split[0]
255 277 if alias in notebook_aliases:
256 278 self.kernel_argv.remove(a)
257 279 if len(split) == 1:
258 280 # alias passed with arg via space
259 281 swallow_next = True
260 282
261 283 def init_configurables(self):
262 284 # Don't let Qt or ZMQ swallow KeyboardInterupts.
263 285 signal.signal(signal.SIGINT, signal.SIG_DFL)
264 286
265 287 # force Session default to be secure
266 288 default_secure(self.config)
267 289 # Create a KernelManager and start a kernel.
268 290 self.kernel_manager = MappingKernelManager(
269 291 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
270 292 connection_dir = self.profile_dir.security_dir,
271 293 )
272 294 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
273 295 self.notebook_manager.list_notebooks()
274 296
275 297 def init_logging(self):
276 298 super(NotebookApp, self).init_logging()
277 299 # This prevents double log messages because tornado use a root logger that
278 300 # self.log is a child of. The logging module dipatches log messages to a log
279 301 # and all of its ancenstors until propagate is set to False.
280 302 self.log.propagate = False
281 303
282 304 @catch_config_error
283 305 def initialize(self, argv=None):
284 306 super(NotebookApp, self).initialize(argv)
285 307 self.init_configurables()
286 308 self.web_app = NotebookWebApplication(
287 309 self, self.kernel_manager, self.notebook_manager, self.log
288 310 )
289 311 if self.certfile:
290 312 ssl_options = dict(certfile=self.certfile)
291 313 if self.keyfile:
292 314 ssl_options['keyfile'] = self.keyfile
293 315 else:
294 316 ssl_options = None
295 317 self.web_app.password = self.password
296 318 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
297 319 if ssl_options is None and not self.ip:
298 320 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
299 321 'but not using any encryption or authentication. This is highly '
300 322 'insecure and not recommended.')
301 323
302 324 # Try random ports centered around the default.
303 325 from random import randint
304 326 n = 50 # Max number of attempts, keep reasonably large.
305 327 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
306 328 try:
307 329 self.http_server.listen(port, self.ip)
308 330 except socket.error, e:
309 331 if e.errno != errno.EADDRINUSE:
310 332 raise
311 333 self.log.info('The port %i is already in use, trying another random port.' % port)
312 334 else:
313 335 self.port = port
314 336 break
315 337
316 338 def start(self):
317 339 ip = self.ip if self.ip else '[all ip addresses on your system]'
318 340 proto = 'https' if self.certfile else 'http'
319 341 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
320 342 ip,
321 343 self.port))
322 344 if self.open_browser:
323 345 ip = self.ip or '127.0.0.1'
324 346 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
325 347 new=2)
326 348 threading.Thread(target=b).start()
327 349
328 350 ioloop.IOLoop.instance().start()
329 351
330 352 #-----------------------------------------------------------------------------
331 353 # Main entry point
332 354 #-----------------------------------------------------------------------------
333 355
334 356 def launch_new_instance():
335 357 app = NotebookApp()
336 358 app.initialize()
337 359 app.start()
338 360
@@ -1,436 +1,448
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_span {
119 119 float: right;
120 120 }
121 121
122 122 #tooltipontab_span {
123 123 float: right;
124 124 }
125 125
126 126 #smartcompleter_span {
127 127 float: right;
128 128 }
129 129
130 130 .checkbox_label {
131 131 font-size: 85%;
132 132 float: right;
133 133 padding: 0.3em;
134 134 }
135 135
136 136 .section_row_header {
137 137 float: left;
138 138 font-size: 85%;
139 139 padding: 0.4em 0em;
140 140 font-weight: bold;
141 141 }
142 142
143 143 span.button_label {
144 144 padding: 0.2em 1em;
145 145 font-size: 77%;
146 146 float: right;
147 147 }
148 148
149 149 /* This is needed because FF was adding a 2px margin top and bottom. */
150 150 .section_row .ui-button {
151 151 margin-top: 0px;
152 152 margin-bottom: 0px;
153 153 }
154 154
155 155 #download_format {
156 156 float: right;
157 157 font-size: 85%;
158 158 width: 62px;
159 159 margin: 1px 5px;
160 160 }
161 161
162 162 div#left_panel_splitter {
163 163 width: 8px;
164 164 top: 0px;
165 165 left: 202px;
166 166 margin: 0px;
167 167 padding: 0px;
168 168 position: absolute;
169 169 }
170 170
171 171 div#notebook_panel {
172 172 /* The L margin will be set in the Javascript code*/
173 173 margin: 0px 0px 0px 0px;
174 174 padding: 0px;
175 175 }
176 176
177 177 div#notebook {
178 178 overflow-y: scroll;
179 179 overflow-x: auto;
180 180 width: 100%;
181 181 /* This spaces the cell away from the edge of the notebook area */
182 182 padding: 5px 5px 15px 5px;
183 183 margin: 0px
184 184 background-color: white;
185 185 }
186 186
187 187 div#pager_splitter {
188 188 height: 8px;
189 189 }
190 190
191 191 div#pager {
192 192 padding: 15px;
193 193 overflow: auto;
194 194 }
195 195
196 196 div.cell {
197 197 width: 100%;
198 198 padding: 5px 5px 5px 0px;
199 199 /* This acts as a spacer between cells, that is outside the border */
200 200 margin: 2px 0px 2px 0px;
201 201 }
202 202
203 203 div.code_cell {
204 204 background-color: white;
205 205 }
206 206 /* any special styling for code cells that are currently running goes here */
207 207 div.code_cell.running {
208 208 }
209 209
210 210 div.prompt {
211 211 /* This needs to be wide enough for 3 digit prompt numbers: In[100]: */
212 212 width: 11ex;
213 213 /* This 0.4em is tuned to match the padding on the CodeMirror editor. */
214 214 padding: 0.4em;
215 215 margin: 0px;
216 216 font-family: monospace;
217 217 text-align:right;
218 218 }
219 219
220 220 div.input {
221 221 page-break-inside: avoid;
222 222 }
223 223
224 224 /* input_area and input_prompt must match in top border and margin for alignment */
225 225 div.input_area {
226 226 color: black;
227 227 border: 1px solid #ddd;
228 228 border-radius: 3px;
229 229 background: #f7f7f7;
230 230 }
231 231
232 232 div.input_prompt {
233 233 color: navy;
234 234 border-top: 1px solid transparent;
235 235 }
236 236
237 237 div.output {
238 238 /* This is a spacer between the input and output of each cell */
239 239 margin-top: 5px;
240 240 }
241 241
242 242 div.output_prompt {
243 243 color: darkred;
244 244 }
245 245
246 246 /* This class is the outer container of all output sections. */
247 247 div.output_area {
248 248 padding: 0px;
249 249 page-break-inside: avoid;
250 250 }
251 251
252 252 /* This class is for the output subarea inside the output_area and after
253 253 the prompt div. */
254 254 div.output_subarea {
255 255 padding: 0.4em 6.1em 0.4em 0.4em;
256 256 }
257 257
258 258 /* The rest of the output_* classes are for special styling of the different
259 259 output types */
260 260
261 261 /* all text output has this class: */
262 262 div.output_text {
263 263 text-align: left;
264 264 color: black;
265 265 font-family: monospace;
266 266 }
267 267
268 268 /* stdout/stderr are 'text' as well as 'stream', but pyout/pyerr are *not* streams */
269 269 div.output_stream {
270 270 padding-top: 0.0em;
271 271 padding-bottom: 0.0em;
272 272 }
273 273 div.output_stdout {
274 274 }
275 275 div.output_stderr {
276 276 background: #fdd; /* very light red background for stderr */
277 277 }
278 278
279 279 div.output_latex {
280 280 text-align: left;
281 281 color: black;
282 282 }
283 283
284 284 div.output_html {
285 285 }
286 286
287 287 div.output_png {
288 288 }
289 289
290 290 div.output_jpeg {
291 291 }
292 292
293 293 div.text_cell {
294 294 background-color: white;
295 295 }
296 296
297 297 div.text_cell_input {
298 298 color: black;
299 299 }
300 300
301 301 div.text_cell_render {
302 302 font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
303 303 outline: none;
304 304 resize: none;
305 305 width: inherit;
306 306 border-style: none;
307 307 padding: 5px;
308 308 color: black;
309 309 }
310 310
311 311 .CodeMirror {
312 312 line-height: 1.231; /* Changed from 1em to our global default */
313 313 }
314 314
315 315 .CodeMirror-scroll {
316 316 height: auto; /* Changed to auto to autogrow */
317 317 /* The CodeMirror docs are a bit fuzzy on if overflow-y should be hidden or visible.*/
318 318 /* We have found that if it is visible, vertical scrollbars appear with font size changes.*/
319 319 overflow-y: hidden;
320 320 overflow-x: auto; /* Changed from auto to remove scrollbar */
321 321 }
322 322
323 323 /* CSS font colors for translated ANSI colors. */
324 324
325 325
326 326 .ansiblack {color: black;}
327 327 .ansired {color: darkred;}
328 328 .ansigreen {color: darkgreen;}
329 329 .ansiyellow {color: brown;}
330 330 .ansiblue {color: darkblue;}
331 331 .ansipurple {color: darkviolet;}
332 332 .ansicyan {color: steelblue;}
333 333 .ansigrey {color: grey;}
334 334 .ansibold {font-weight: bold;}
335 335
336 336 .completions , .tooltip{
337 337 position: absolute;
338 338 z-index: 10;
339 339 overflow: auto;
340 340 border: 1px solid black;
341 341 }
342 342
343 343 .completions select {
344 344 background: white;
345 345 outline: none;
346 346 border: none;
347 347 padding: 0px;
348 348 margin: 0px;
349 349 font-family: monospace;
350 350 }
351 351
352 352 @-moz-keyframes fadeIn {
353 353 from {opacity:0;}
354 354 to {opacity:1;}
355 355 }
356 356
357 357 @-webkit-keyframes fadeIn {
358 358 from {opacity:0;}
359 359 to {opacity:1;}
360 360 }
361 361
362 362 @keyframes fadeIn {
363 363 from {opacity:0;}
364 364 to {opacity:1;}
365 365 }
366 366
367 367 /*"close" "expand" and "Open in pager button" of
368 368 /* the tooltip*/
369 369 .tooltip a{
370 370 float:right;
371 371 }
372 372
373 373 /*properties of tooltip after "expand"*/
374 374 .bigtooltip{
375 375 height:30%;
376 376 }
377 377
378 378 /*properties of tooltip before "expand"*/
379 379 .smalltooltip{
380 380 text-overflow: ellipsis;
381 381 overflow: hidden;
382 382 height:15%;
383 383 }
384 384
385 385 .tooltip{
386 386 /*transition when "expand"ing tooltip */
387 387 -webkit-transition-property: height;
388 388 -webkit-transition-duration: 1s;
389 389 -moz-transition-property: height;
390 390 -moz-transition-duration: 1s;
391 391 transition-property: height;
392 392 transition-duration: 1s;
393 393 max-width:700px;
394 394 border-radius: 0px 10px 10px 10px;
395 395 box-shadow: 3px 3px 5px #999;
396 396 /*fade-in animation when inserted*/
397 397 -webkit-animation: fadeIn 200ms;
398 398 -moz-animation: fadeIn 200ms;
399 399 animation: fadeIn 200ms;
400 400 vertical-align: middle;
401 401 background: #FDFDD8;
402 402 outline: none;
403 403 padding: 3px;
404 404 margin: 0px;
405 405 font-family: monospace;
406 406 min-height:50px;
407 407 }
408 408
409 409 .completions p{
410 410 background: #DDF;
411 411 /*outline: none;
412 412 padding: 0px;*/
413 413 border-bottom: black solid 1px;
414 414 padding: 1px;
415 415 font-family: monospace;
416 416 }
417 417
418 pre.dialog {
419 background-color: #f7f7f7;
420 border: 1px solid #ddd;
421 border-radius: 3px;
422 padding: 0.4em;
423 padding-left: 2em;
424 }
425
426 p.dialog{
427 padding : 0.2em;
428 }
429
418 430 @media print {
419 431 body { overflow: visible !important; }
420 432 .ui-widget-content { border: 0px; }
421 433 }
422 434
423 435 .shortcut_key {
424 436 display: inline-block;
425 437 width: 13ex;
426 438 text-align: right;
427 439 font-family: monospace;
428 440 }
429 441
430 442 .shortcut_descr {
431 443 }
432 444
433 445 /* Word-wrap output correctly. This is the CSS3 spelling, though Firefox seems
434 446 to not honor it correctly. Webkit browsers (Chrome, rekonq, Safari) do.
435 447 */
436 448 pre, code, kbd, samp { white-space: pre-wrap; }
@@ -1,96 +1,103
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,795 +1,795
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 IPython.Cell.apply(this, arguments);
24 24 };
25 25
26 26
27 27 CodeCell.prototype = new IPython.Cell();
28 28
29 29
30 30 CodeCell.prototype.create_element = function () {
31 31 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
32 32 cell.attr('tabindex','2');
33 33 var input = $('<div></div>').addClass('input hbox');
34 34 input.append($('<div/>').addClass('prompt input_prompt'));
35 35 var input_area = $('<div/>').addClass('input_area box-flex1');
36 36 this.code_mirror = CodeMirror(input_area.get(0), {
37 37 indentUnit : 4,
38 38 mode: 'python',
39 39 theme: 'ipython',
40 40 readOnly: this.read_only,
41 41 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
42 42 });
43 43 input.append(input_area);
44 44 var output = $('<div></div>').addClass('output vbox');
45 45 cell.append(input).append(output);
46 46 this.element = cell;
47 47 this.collapse();
48 48 };
49 49
50 50 //TODO, try to diminish the number of parameters.
51 51 CodeCell.prototype.request_tooltip_after_time = function (pre_cursor,time,that){
52 52 if (pre_cursor === "" || pre_cursor === "(" ) {
53 53 // don't do anything if line beggin with '(' or is empty
54 54 } else {
55 55 // Will set a timer to request tooltip in `time`
56 56 that.tooltip_timeout = setTimeout(function(){
57 57 IPython.notebook.request_tool_tip(that, pre_cursor)
58 58 },time);
59 59 }
60 60 };
61 61
62 62 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
63 63 // This method gets called in CodeMirror's onKeyDown/onKeyPress
64 64 // handlers and is used to provide custom key handling. Its return
65 65 // value is used to determine if CodeMirror should ignore the event:
66 66 // true = ignore, false = don't ignore.
67 67
68 68 // note that we are comparing and setting the time to wait at each key press.
69 69 // a better wqy might be to generate a new function on each time change and
70 70 // assign it to CodeCell.prototype.request_tooltip_after_time
71 71 tooltip_wait_time = this.notebook.time_before_tooltip;
72 72 tooltip_on_tab = this.notebook.tooltip_on_tab;
73 73 var that = this;
74 74 // whatever key is pressed, first, cancel the tooltip request before
75 75 // they are sent, and remove tooltip if any
76 76 if(event.type === 'keydown' ){
77 77 CodeCell.prototype.remove_and_cancel_tooltip(that.tooltip_timeout);
78 78 that.tooltip_timeout=null;
79 79 }
80 80
81 81 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
82 82 // Always ignore shift-enter in CodeMirror as we handle it.
83 83 return true;
84 84 }else if (event.which === 40 && event.type === 'keypress' && tooltip_wait_time >= 0) {
85 85 // triger aon keypress (!) otherwise inconsistent event.which depending on plateform
86 86 // browser and keyboard layout !
87 87 // Pressing '(' , request tooltip, don't forget to reappend it
88 88 var cursor = editor.getCursor();
89 89 var pre_cursor = editor.getRange({line:cursor.line,ch:0},cursor).trim()+'(';
90 90 CodeCell.prototype.request_tooltip_after_time(pre_cursor,tooltip_wait_time,that);
91 91 } else if (event.keyCode === 9 && event.type == 'keydown') {
92 92 // Tab completion.
93 93 var cur = editor.getCursor();
94 94 //Do not trim here because of tooltip
95 95 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
96 96 if (pre_cursor.trim() === "") {
97 97 // Don't autocomplete if the part of the line before the cursor
98 98 // is empty. In this case, let CodeMirror handle indentation.
99 99 return false;
100 100 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && tooltip_on_tab ) {
101 101 CodeCell.prototype.request_tooltip_after_time(pre_cursor,0,that);
102 102 } else {
103 103 pre_cursor.trim();
104 104 // Autocomplete the current line.
105 105 event.stop();
106 106 var line = editor.getLine(cur.line);
107 107 this.is_completing = true;
108 108 this.completion_cursor = cur;
109 109 IPython.notebook.complete_cell(this, line, cur.ch);
110 110 return true;
111 111 }
112 112 } else if (event.keyCode === 8 && event.type == 'keydown') {
113 113 // If backspace and the line ends with 4 spaces, remove them.
114 114 var cur = editor.getCursor();
115 115 var line = editor.getLine(cur.line);
116 116 var ending = line.slice(-4);
117 117 if (ending === ' ') {
118 118 editor.replaceRange('',
119 119 {line: cur.line, ch: cur.ch-4},
120 120 {line: cur.line, ch: cur.ch}
121 121 );
122 122 event.stop();
123 123 return true;
124 124 } else {
125 125 return false;
126 126 }
127 127 } else if (event.keyCode === 76 && event.ctrlKey && event.shiftKey
128 128 && event.type == 'keydown') {
129 129 // toggle line numbers with Ctrl-Shift-L
130 130 this.toggle_line_numbers();
131 131 }
132 132 else {
133 133 // keypress/keyup also trigger on TAB press, and we don't want to
134 134 // use those to disable tab completion.
135 135 if (this.is_completing && event.keyCode !== 9) {
136 136 var ed_cur = editor.getCursor();
137 137 var cc_cur = this.completion_cursor;
138 138 if (ed_cur.line !== cc_cur.line || ed_cur.ch !== cc_cur.ch) {
139 139 this.is_completing = false;
140 140 this.completion_cursor = null;
141 141 }
142 142 }
143 143 return false;
144 144 };
145 145 return false;
146 146 };
147 147
148 148 CodeCell.prototype.remove_and_cancel_tooltip = function(timeout)
149 149 {
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(timeout != null)
154 154 { clearTimeout(timeout);}
155 155 $('#tooltip').remove();
156 156 }
157 157
158 158 CodeCell.prototype.finish_tooltip = function (reply) {
159 159 defstring=reply.definition;
160 160 docstring=reply.docstring;
161 161 if(docstring == null){docstring="<empty docstring>"};
162 162 name=reply.name;
163 163
164 164 var that = this;
165 165 var tooltip = $('<div/>').attr('id', 'tooltip').addClass('tooltip');
166 166 // remove to have the tooltip not Limited in X and Y
167 167 tooltip.addClass('smalltooltip');
168 168 var pre=$('<pre/>').html(utils.fixConsole(docstring));
169 169 var expandlink=$('<a/>').attr('href',"#");
170 170 expandlink.addClass("ui-corner-all"); //rounded corner
171 171 expandlink.attr('role',"button");
172 172 //expandlink.addClass('ui-button');
173 173 //expandlink.addClass('ui-state-default');
174 174 var expandspan=$('<span/>').text('Expand');
175 175 expandspan.addClass('ui-icon');
176 176 expandspan.addClass('ui-icon-plus');
177 177 expandlink.append(expandspan);
178 178 expandlink.attr('id','expanbutton');
179 179 expandlink.click(function(){
180 180 tooltip.removeClass('smalltooltip');
181 181 tooltip.addClass('bigtooltip');
182 182 $('#expanbutton').remove();
183 183 setTimeout(function(){that.code_mirror.focus();}, 50);
184 184 });
185 185 var morelink=$('<a/>').attr('href',"#");
186 186 morelink.attr('role',"button");
187 187 morelink.addClass('ui-button');
188 188 //morelink.addClass("ui-corner-all"); //rounded corner
189 189 //morelink.addClass('ui-state-default');
190 190 var morespan=$('<span/>').text('Open in Pager');
191 191 morespan.addClass('ui-icon');
192 192 morespan.addClass('ui-icon-arrowstop-l-n');
193 193 morelink.append(morespan);
194 194 morelink.click(function(){
195 195 var msg_id = IPython.notebook.kernel.execute(name+"?");
196 196 IPython.notebook.msg_cell_map[msg_id] = IPython.notebook.selected_cell().cell_id;
197 197 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
198 198 setTimeout(function(){that.code_mirror.focus();}, 50);
199 199 });
200 200
201 201 var closelink=$('<a/>').attr('href',"#");
202 202 closelink.attr('role',"button");
203 203 closelink.addClass('ui-button');
204 204 //closelink.addClass("ui-corner-all"); //rounded corner
205 205 //closelink.adClass('ui-state-default'); // grey background and blue cross
206 206 var closespan=$('<span/>').text('Close');
207 207 closespan.addClass('ui-icon');
208 208 closespan.addClass('ui-icon-close');
209 209 closelink.append(closespan);
210 210 closelink.click(function(){
211 211 CodeCell.prototype.remove_and_cancell_tooltip(that.tooltip_timeout);
212 212 setTimeout(function(){that.code_mirror.focus();}, 50);
213 213 });
214 214 //construct the tooltip
215 215 tooltip.append(closelink);
216 216 tooltip.append(expandlink);
217 217 tooltip.append(morelink);
218 218 if(defstring){
219 219 defstring_html= $('<pre/>').html(utils.fixConsole(defstring));
220 220 tooltip.append(defstring_html);
221 221 }
222 222 tooltip.append(pre);
223 223 var pos = this.code_mirror.cursorCoords();
224 224 tooltip.css('left',pos.x+'px');
225 225 tooltip.css('top',pos.yBot+'px');
226 226 $('body').append(tooltip);
227 227
228 228 // issues with cross-closing if multiple tooltip in less than 5sec
229 229 // keep it comented for now
230 230 // setTimeout(CodeCell.prototype.remove_and_cancell_tooltip, 5000);
231 231 };
232 232
233 233 // As you type completer
234 234 CodeCell.prototype.finish_completing = function (matched_text, matches) {
235 235 //return if not completing or nothing to complete
236 236 if (!this.is_completing || matches.length === 0) {return;}
237 237
238 238 // for later readability
239 239 var key = { tab:9,
240 240 esc:27,
241 241 backspace:8,
242 242 space:13,
243 243 shift:16,
244 244 enter:32,
245 245 // _ is 189
246 246 isCompSymbol : function (code)
247 247 {return ((code>64 && code <=122)|| code == 189)}
248 248 }
249 249
250 250 // smart completion, sort kwarg ending with '='
251 251 var newm = new Array();
252 252 if(this.notebook.smart_completer)
253 253 {
254 254 kwargs = new Array();
255 255 other = new Array();
256 256 for(var i=0;i<matches.length; ++i){
257 257 if(matches[i].substr(-1) === '='){
258 258 kwargs.push(matches[i]);
259 259 }else{other.push(matches[i]);}
260 260 }
261 261 newm = kwargs.concat(other);
262 262 matches=newm;
263 263 }
264 264 // end sort kwargs
265 265
266 266 // give common prefix of a array of string
267 267 function sharedStart(A){
268 268 if(A.length > 1 ){
269 269 var tem1, tem2, s, A= A.slice(0).sort();
270 270 tem1= A[0];
271 271 s= tem1.length;
272 272 tem2= A.pop();
273 273 while(s && tem2.indexOf(tem1)== -1){
274 274 tem1= tem1.substring(0, --s);
275 275 }
276 276 return tem1;
277 277 }
278 278 return "";
279 279 }
280 280
281 281
282 282 //try to check if the user is typing tab at least twice after a word
283 283 // and completion is "done"
284 284 fallback_on_tooltip_after=2
285 285 if(matches.length==1 && matched_text === matches[0])
286 286 {
287 287 if(this.npressed >fallback_on_tooltip_after && this.prevmatch==matched_text)
288 288 {
289 289 console.log('Ok, you really want to complete after pressing tab '+this.npressed+' times !');
290 290 console.log('You should understand that there is no (more) completion for that !');
291 291 console.log("I'll show you the tooltip, will you stop bothering me ?");
292 292 this.request_tooltip_after_time(matched_text+'(',0,this);
293 293 return;
294 294 }
295 295 this.prevmatch=matched_text
296 296 this.npressed=this.npressed+1;
297 297 }
298 298 else
299 299 {
300 300 this.prevmatch="";
301 301 this.npressed=0;
302 302 }
303 303 // end fallback on tooltip
304 304 //==================================
305 305 // Real completion logic start here
306 306 var that = this;
307 307 var cur = this.completion_cursor;
308 308 var done = false;
309 309
310 310 // call to dismmiss the completer
311 311 var close = function () {
312 312 if (done) return;
313 313 done = true;
314 314 if (complete!=undefined)
315 315 {complete.remove();}
316 316 that.is_completing = false;
317 317 that.completion_cursor = null;
318 318 };
319 319
320 320 // insert the given text and exit the completer
321 321 var insert = function (selected_text, event) {
322 322 that.code_mirror.replaceRange(
323 323 selected_text,
324 324 {line: cur.line, ch: (cur.ch-matched_text.length)},
325 325 {line: cur.line, ch: cur.ch}
326 326 );
327 327 if(event != null){
328 328 event.stopPropagation();
329 329 event.preventDefault();
330 330 }
331 331 close();
332 332 setTimeout(function(){that.code_mirror.focus();}, 50);
333 333 };
334 334
335 335 // insert the curent highlited selection and exit
336 336 var pick = function () {
337 337 insert(select.val()[0],null);
338 338 };
339 339
340 340
341 341 // Define function to clear the completer, refill it with the new
342 342 // matches, update the pseuso typing field. autopick insert match if
343 343 // only one left, in no matches (anymore) dismiss itself by pasting
344 344 // what the user have typed until then
345 345 var complete_with = function(matches,typed_text,autopick,event)
346 346 {
347 347 // If autopick an only one match, past.
348 348 // Used to 'pick' when pressing tab
349 349 if (matches.length < 1) {
350 350 insert(typed_text,event);
351 351 if(event !=null){
352 352 event.stopPropagation();
353 353 event.preventDefault();
354 354 }
355 355 } else if (autopick && matches.length==1) {
356 356 insert(matches[0],event);
357 357 if(event !=null){
358 358 event.stopPropagation();
359 359 event.preventDefault();
360 360 }
361 361 }
362 362 //clear the previous completion if any
363 363 complete.children().children().remove();
364 364 $('#asyoutype').text(typed_text);
365 365 select=$('#asyoutypeselect');
366 366 for (var i=0; i<matches.length; ++i) {
367 367 select.append($('<option/>').html(matches[i]));
368 368 }
369 369 select.children().first().attr('selected','true');
370 370 }
371 371
372 372 // create html for completer
373 373 var complete = $('<div/>').addClass('completions');
374 374 complete.attr('id','complete');
375 375 complete.append($('<p/>').attr('id', 'asyoutype').html(matched_text));//pseudo input field
376 376
377 377 var select = $('<select/>').attr('multiple','true');
378 378 select.attr('id', 'asyoutypeselect')
379 379 select.attr('size',Math.min(10,matches.length));
380 380 var pos = this.code_mirror.cursorCoords();
381 381
382 382 // TODO: I propose to remove enough horizontal pixel
383 383 // to align the text later
384 384 complete.css('left',pos.x+'px');
385 385 complete.css('top',pos.yBot+'px');
386 386 complete.append(select);
387 387
388 388 $('body').append(complete);
389 389
390 390 // So a first actual completion. see if all the completion start wit
391 391 // the same letter and complete if necessary
392 392 fastForward = sharedStart(matches)
393 393 typed_characters= fastForward.substr(matched_text.length);
394 394 complete_with(matches,matched_text+typed_characters,true,null);
395 395 filterd=matches;
396 396 // Give focus to select, and make it filter the match as the user type
397 397 // by filtering the previous matches. Called by .keypress and .keydown
398 398 var downandpress = function (event,press_or_down) {
399 399 var code = event.which;
400 400 var autopick = false; // auto 'pick' if only one match
401 401 if (press_or_down === 0){
402 402 press=true; down=false; //Are we called from keypress or keydown
403 403 } else if (press_or_down == 1){
404 404 press=false; down=true;
405 405 }
406 406 if (code === key.shift) {
407 407 // nothing on Shift
408 408 return;
409 409 }
410 410 if (code === key.space || code === key.enter) {
411 411 // Pressing SPACE or ENTER will cause a pick
412 412 event.stopPropagation();
413 413 event.preventDefault();
414 414 pick();
415 415 } else if (code === 38 || code === 40) {
416 416 // We don't want the document keydown handler to handle UP/DOWN,
417 417 // but we want the default action.
418 418 event.stopPropagation();
419 419 //} else if ( key.isCompSymbol(code)|| (code==key.backspace)||(code==key.tab && down)){
420 420 } else if ( (code==key.backspace)||(code==key.tab && down) || press || key.isCompSymbol(code)){
421 421 if( key.isCompSymbol(code) && press)
422 422 {
423 423 var newchar = String.fromCharCode(code);
424 424 typed_characters=typed_characters+newchar;
425 425 } else if (code == key.tab) {
426 426 fastForward = sharedStart(filterd)
427 427 ffsub = fastForward.substr(matched_text.length+typed_characters.length);
428 428 typed_characters=typed_characters+ffsub;
429 429 autopick=true;
430 430 event.stopPropagation();
431 431 event.preventDefault();
432 432 } else if (code == key.backspace && down) {
433 433 // cancel if user have erase everything, otherwise decrease
434 434 // what we filter with
435 435 if (typed_characters.length <= 0)
436 436 {
437 437 insert(matched_text,event)
438 438 }
439 439 typed_characters=typed_characters.substr(0,typed_characters.length-1);
440 440 }else{return}
441 441 re = new RegExp("^"+"\%?"+matched_text+typed_characters,"");
442 442 filterd = matches.filter(function(x){return re.test(x)});
443 443 complete_with(filterd,matched_text+typed_characters,autopick,event);
444 444 } else if(down){ // abort only on .keydown
445 445 // abort with what the user have pressed until now
446 446 console.log('aborting with keycode : '+code+' is down :'+down);
447 447 insert(matched_text+typed_characters,event);
448 448 }
449 449 }
450 450 select.keydown(function (event) {
451 451 downandpress(event,1)
452 452 });
453 453 select.keypress(function (event) {
454 454 downandpress(event,0)
455 455 });
456 456 // Double click also causes a pick.
457 457 // and bind the last actions.
458 458 select.dblclick(pick);
459 459 select.blur(close);
460 460 select.focus();
461 461 };
462 462
463 463 CodeCell.prototype.toggle_line_numbers = function () {
464 464 if (this.code_mirror.getOption('lineNumbers') == false) {
465 465 this.code_mirror.setOption('lineNumbers', true);
466 466 } else {
467 467 this.code_mirror.setOption('lineNumbers', false);
468 468 }
469 469 this.code_mirror.refresh();
470 470 };
471 471
472 472 CodeCell.prototype.select = function () {
473 473 IPython.Cell.prototype.select.apply(this);
474 474 // Todo: this dance is needed because as of CodeMirror 2.12, focus is
475 475 // not causing the cursor to blink if the editor is empty initially.
476 476 // While this seems to fix the issue, this should be fixed
477 477 // in CodeMirror proper.
478 478 var s = this.code_mirror.getValue();
479 479 this.code_mirror.focus();
480 480 if (s === '') this.code_mirror.setValue('');
481 481 };
482 482
483 483
484 484 CodeCell.prototype.select_all = function () {
485 485 var start = {line: 0, ch: 0};
486 486 var nlines = this.code_mirror.lineCount();
487 487 var last_line = this.code_mirror.getLine(nlines-1);
488 488 var end = {line: nlines-1, ch: last_line.length};
489 489 this.code_mirror.setSelection(start, end);
490 490 };
491 491
492 492
493 493 CodeCell.prototype.append_output = function (json) {
494 494 this.expand();
495 495 if (json.output_type === 'pyout') {
496 496 this.append_pyout(json);
497 497 } else if (json.output_type === 'pyerr') {
498 498 this.append_pyerr(json);
499 499 } else if (json.output_type === 'display_data') {
500 500 this.append_display_data(json);
501 501 } else if (json.output_type === 'stream') {
502 502 this.append_stream(json);
503 503 };
504 504 this.outputs.push(json);
505 505 };
506 506
507 507
508 508 CodeCell.prototype.create_output_area = function () {
509 509 var oa = $("<div/>").addClass("hbox output_area");
510 510 oa.append($('<div/>').addClass('prompt'));
511 511 return oa;
512 512 };
513 513
514 514
515 515 CodeCell.prototype.append_pyout = function (json) {
516 516 n = json.prompt_number || ' ';
517 517 var toinsert = this.create_output_area();
518 518 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
519 519 this.append_mime_type(json, toinsert);
520 520 this.element.find('div.output').append(toinsert);
521 521 // If we just output latex, typeset it.
522 522 if ((json.latex !== undefined) || (json.html !== undefined)) {
523 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
523 this.typeset();
524 524 };
525 525 };
526 526
527 527
528 528 CodeCell.prototype.append_pyerr = function (json) {
529 529 var tb = json.traceback;
530 530 if (tb !== undefined && tb.length > 0) {
531 531 var s = '';
532 532 var len = tb.length;
533 533 for (var i=0; i<len; i++) {
534 534 s = s + tb[i] + '\n';
535 535 }
536 536 s = s + '\n';
537 537 var toinsert = this.create_output_area();
538 538 this.append_text(s, toinsert);
539 539 this.element.find('div.output').append(toinsert);
540 540 };
541 541 };
542 542
543 543
544 544 CodeCell.prototype.append_stream = function (json) {
545 545 // temporary fix: if stream undefined (json file written prior to this patch),
546 546 // default to most likely stdout:
547 547 if (json.stream == undefined){
548 548 json.stream = 'stdout';
549 549 }
550 550 var subclass = "output_"+json.stream;
551 551 if (this.outputs.length > 0){
552 552 // have at least one output to consider
553 553 var last = this.outputs[this.outputs.length-1];
554 554 if (last.output_type == 'stream' && json.stream == last.stream){
555 555 // latest output was in the same stream,
556 556 // so append directly into its pre tag
557 557 this.element.find('div.'+subclass).last().find('pre').append(json.text);
558 558 return;
559 559 }
560 560 }
561 561
562 562 // If we got here, attach a new div
563 563 var toinsert = this.create_output_area();
564 564 this.append_text(json.text, toinsert, "output_stream "+subclass);
565 565 this.element.find('div.output').append(toinsert);
566 566 };
567 567
568 568
569 569 CodeCell.prototype.append_display_data = function (json) {
570 570 var toinsert = this.create_output_area();
571 571 this.append_mime_type(json, toinsert);
572 572 this.element.find('div.output').append(toinsert);
573 573 // If we just output latex, typeset it.
574 574 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
575 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
575 this.typeset();
576 576 };
577 577 };
578 578
579 579
580 580 CodeCell.prototype.append_mime_type = function (json, element) {
581 581 if (json.html !== undefined) {
582 582 this.append_html(json.html, element);
583 583 } else if (json.latex !== undefined) {
584 584 this.append_latex(json.latex, element);
585 585 } else if (json.svg !== undefined) {
586 586 this.append_svg(json.svg, element);
587 587 } else if (json.png !== undefined) {
588 588 this.append_png(json.png, element);
589 589 } else if (json.jpeg !== undefined) {
590 590 this.append_jpeg(json.jpeg, element);
591 591 } else if (json.text !== undefined) {
592 592 this.append_text(json.text, element);
593 593 };
594 594 };
595 595
596 596
597 597 CodeCell.prototype.append_html = function (html, element) {
598 598 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_html rendered_html");
599 599 toinsert.append(html);
600 600 element.append(toinsert);
601 601 };
602 602
603 603
604 604 CodeCell.prototype.append_text = function (data, element, extra_class) {
605 605 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_text");
606 606 if (extra_class){
607 607 toinsert.addClass(extra_class);
608 608 }
609 609 toinsert.append($("<pre/>").html(data));
610 610 element.append(toinsert);
611 611 };
612 612
613 613
614 614 CodeCell.prototype.append_svg = function (svg, element) {
615 615 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_svg");
616 616 toinsert.append(svg);
617 617 element.append(toinsert);
618 618 };
619 619
620 620
621 621 CodeCell.prototype.append_png = function (png, element) {
622 622 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_png");
623 623 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
624 624 element.append(toinsert);
625 625 };
626 626
627 627
628 628 CodeCell.prototype.append_jpeg = function (jpeg, element) {
629 629 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_jpeg");
630 630 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
631 631 element.append(toinsert);
632 632 };
633 633
634 634
635 635 CodeCell.prototype.append_latex = function (latex, element) {
636 636 // This method cannot do the typesetting because the latex first has to
637 637 // be on the page.
638 638 var toinsert = $("<div/>").addClass("box_flex1 output_subarea output_latex");
639 639 toinsert.append(latex);
640 640 element.append(toinsert);
641 641 };
642 642
643 643
644 644 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
645 645 var output_div = this.element.find("div.output");
646 646 if (stdout && stderr && other){
647 647 // clear all, no need for logic
648 648 output_div.html("");
649 649 this.outputs = [];
650 650 return;
651 651 }
652 652 // remove html output
653 653 // each output_subarea that has an identifying class is in an output_area
654 654 // which is the element to be removed.
655 655 if (stdout){
656 656 output_div.find("div.output_stdout").parent().remove();
657 657 }
658 658 if (stderr){
659 659 output_div.find("div.output_stderr").parent().remove();
660 660 }
661 661 if (other){
662 662 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
663 663 }
664 664
665 665 // remove cleared outputs from JSON list:
666 666 for (var i = this.outputs.length - 1; i >= 0; i--){
667 667 var out = this.outputs[i];
668 668 var output_type = out.output_type;
669 669 if (output_type == "display_data" && other){
670 670 this.outputs.splice(i,1);
671 671 }else if (output_type == "stream"){
672 672 if (stdout && out.stream == "stdout"){
673 673 this.outputs.splice(i,1);
674 674 }else if (stderr && out.stream == "stderr"){
675 675 this.outputs.splice(i,1);
676 676 }
677 677 }
678 678 }
679 679 };
680 680
681 681
682 682 CodeCell.prototype.clear_input = function () {
683 683 this.code_mirror.setValue('');
684 684 };
685 685
686 686
687 687 CodeCell.prototype.collapse = function () {
688 688 if (!this.collapsed) {
689 689 this.element.find('div.output').hide();
690 690 this.collapsed = true;
691 691 };
692 692 };
693 693
694 694
695 695 CodeCell.prototype.expand = function () {
696 696 if (this.collapsed) {
697 697 this.element.find('div.output').show();
698 698 this.collapsed = false;
699 699 };
700 700 };
701 701
702 702
703 703 CodeCell.prototype.toggle_output = function () {
704 704 if (this.collapsed) {
705 705 this.expand();
706 706 } else {
707 707 this.collapse();
708 708 };
709 709 };
710 710
711 711 CodeCell.prototype.set_input_prompt = function (number) {
712 712 var n = number || '&nbsp;';
713 713 this.input_prompt_number = n;
714 714 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
715 715 };
716 716
717 717
718 718 CodeCell.prototype.get_code = function () {
719 719 return this.code_mirror.getValue();
720 720 };
721 721
722 722
723 723 CodeCell.prototype.set_code = function (code) {
724 724 return this.code_mirror.setValue(code);
725 725 };
726 726
727 727
728 728 CodeCell.prototype.at_top = function () {
729 729 var cursor = this.code_mirror.getCursor();
730 730 if (cursor.line === 0) {
731 731 return true;
732 732 } else {
733 733 return false;
734 734 }
735 735 };
736 736
737 737
738 738 CodeCell.prototype.at_bottom = function () {
739 739 var cursor = this.code_mirror.getCursor();
740 740 if (cursor.line === (this.code_mirror.lineCount()-1)) {
741 741 return true;
742 742 } else {
743 743 return false;
744 744 }
745 745 };
746 746
747 747
748 748 CodeCell.prototype.fromJSON = function (data) {
749 749 console.log('Import from JSON:', data);
750 750 if (data.cell_type === 'code') {
751 751 if (data.input !== undefined) {
752 752 this.set_code(data.input);
753 753 }
754 754 if (data.prompt_number !== undefined) {
755 755 this.set_input_prompt(data.prompt_number);
756 756 } else {
757 757 this.set_input_prompt();
758 758 };
759 759 var len = data.outputs.length;
760 760 for (var i=0; i<len; i++) {
761 761 this.append_output(data.outputs[i]);
762 762 };
763 763 if (data.collapsed !== undefined) {
764 764 if (data.collapsed) {
765 765 this.collapse();
766 766 };
767 767 };
768 768 };
769 769 };
770 770
771 771
772 772 CodeCell.prototype.toJSON = function () {
773 773 var data = {};
774 774 data.input = this.get_code();
775 775 data.cell_type = 'code';
776 776 if (this.input_prompt_number !== ' ') {
777 777 data.prompt_number = this.input_prompt_number;
778 778 };
779 779 var outputs = [];
780 780 var len = this.outputs.length;
781 781 for (var i=0; i<len; i++) {
782 782 outputs[i] = this.outputs[i];
783 783 };
784 784 data.outputs = outputs;
785 785 data.language = 'python';
786 786 data.collapsed = this.collapsed;
787 787 // console.log('Export to JSON:',data);
788 788 return data;
789 789 };
790 790
791 791
792 792 IPython.CodeCell = CodeCell;
793 793
794 794 return IPython;
795 795 }(IPython));
@@ -1,85 +1,131
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 14
15 if (window.MathJax == undefined){
16 // MathJax undefined, but expected. Draw warning.
17 window.MathJax = null;
18 var dialog = $('<div></div>').html(
19 "<p class='dialog'>"+
20 "We were unable to retrieve MathJax. Math/LaTeX rendering will be disabled."+
21 "</p>"+
22 "<p class='dialog'>"+
23 "With a working internet connection, you can run the following at a Python"+
24 " or IPython prompt, which will install a local copy of MathJax:"+
25 "</p>"+
26 "<pre class='dialog'>"+
27 ">>> from IPython.external import mathjax; mathjax.install_mathjax()"+
28 "</pre>"+
29 "<p class='dialog'>"+
30 "This will try to install MathJax into the directory where you installed"+
31 " IPython. If you installed IPython to a location that requires"+
32 " administrative privileges to write, you will need to make this call as"+
33 " an administrator."+
34 "</p>"+
35 "<p class='dialog'>"+
36 "On OSX/Linux/Unix, this can be done at the command-line via:"+
37 "</p>"+
38 "<pre class='dialog'>"+
39 "$ sudo python -c 'from IPython.external import mathjax; mathjax.install_mathjax()'"+
40 "</pre>"+
41 "<p class='dialog'>"+
42 "Or you can instruct the notebook server to start without MathJax support, with:"+
43 "<pre class='dialog'>"+
44 "</p>"+
45 "$ ipython notebook --no-mathjax"+
46 "</pre>"+
47 "<p class='dialog'>"+
48 "in which case, equations will not be rendered."+
49 "</p>"
50 ).dialog({
51 title: 'MathJax disabled',
52 width: "70%",
53 modal: true,
54 })
55 }else if (window.MathJax){
15 56 MathJax.Hub.Config({
16 57 tex2jax: {
17 58 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
18 59 displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
19 60 },
20 61 displayAlign: 'left', // Change this to 'center' to center equations.
21 62 "HTML-CSS": {
22 63 styles: {'.MathJax_Display': {"margin": 0}}
23 64 }
24 65 });
66 }else{
67 // window.MathJax == null
68 // --no-mathjax mode
69 }
70
25 71 IPython.markdown_converter = new Markdown.Converter();
26 72 IPython.read_only = $('meta[name=read_only]').attr("content") == 'True';
27 73
28 74 $('div#header').addClass('border-box-sizing');
29 75 $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content');
30 76 $('div#notebook_panel').addClass('border-box-sizing ui-widget');
31 77
32 78 IPython.layout_manager = new IPython.LayoutManager();
33 79 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
34 80 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
35 81 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
36 82 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
37 83 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
38 84 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
39 85 IPython.notebook = new IPython.Notebook('div#notebook');
40 86 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
41 87 IPython.kernel_status_widget.status_idle();
42 88
43 89 IPython.layout_manager.do_resize();
44 90
45 91 // These have display: none in the css file and are made visible here to prevent FLOUC.
46 92 $('div#header').css('display','block');
47 93
48 94 if(IPython.read_only){
49 95 // hide various elements from read-only view
50 96 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
51 97 IPython.quick_help.element.addClass('hidden'); // shortcuts are disabled in read_only
52 98 $('button#new_notebook').addClass('hidden');
53 99 $('div#cell_section').addClass('hidden');
54 100 $('div#config_section').addClass('hidden');
55 101 $('div#kernel_section').addClass('hidden');
56 102 $('span#login_widget').removeClass('hidden');
57 103 // left panel starts collapsed, but the collapse must happen after
58 104 // elements start drawing. Don't draw contents of the panel until
59 105 // after they are collapsed
60 106 IPython.left_panel.left_panel_element.css('visibility', 'hidden');
61 107 }
62 108
63 109 $('div#main_app').css('display','block');
64 110
65 111 // Perform these actions after the notebook has been loaded.
66 112 // We wait 100 milliseconds because the notebook scrolls to the top after a load
67 113 // is completed and we need to wait for that to mostly finish.
68 114 IPython.notebook.load_notebook(function () {
69 115 setTimeout(function () {
70 116 IPython.save_widget.update_url();
71 117 IPython.layout_manager.do_resize();
72 118 IPython.pager.collapse();
73 119 if(IPython.read_only){
74 120 // collapse the left panel on read-only
75 121 IPython.left_panel.collapse();
76 122 // and finally unhide the panel contents after collapse
77 123 setTimeout(function(){
78 124 IPython.left_panel.left_panel_element.css('visibility', 'visible');
79 125 }, 200);
80 126 }
81 127 },100);
82 128 });
83 129
84 130 });
85 131
@@ -1,272 +1,272
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,327 +1,303
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 {% if enable_mathjax %}
9 10 <!-- <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" charset="utf-8"></script> -->
10 11 <script type='text/javascript' src='static/mathjax/MathJax.js?config=TeX-AMS_HTML' charset='utf-8'></script>
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') {
13 if (typeof(MathJax) == 'undefined') {
22 14 console.log("No local MathJax, loading from CDN");
23 15 document.write(unescape("%3Cscript type='text/javascript' src='http://cdn.mathjax.org/mathjax/latest/MathJax.js%3Fconfig=TeX-AMS_HTML' charset='utf-8'%3E%3C/script%3E"));
24 16 }else{
25 17 console.log("Using local MathJax");
26 18 }
27 19 </script>
20 {% else %}
21 <script type="text/javascript">
22 // MathJax disabled, set as null to distingish from *missing* MathJax,
23 // where it will be undefined, and should prompt a dialog later.
24 window.MathJax = null;
25 </script>
26 {% end %}
28 27
29 28 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
30 29 <link rel="stylesheet" href="static/codemirror/lib/codemirror.css">
31 30 <link rel="stylesheet" href="static/codemirror/mode/markdown/markdown.css">
32 31 <link rel="stylesheet" href="static/codemirror/mode/rst/rst.css">
33 32 <link rel="stylesheet" href="static/codemirror/theme/ipython.css">
34 33 <link rel="stylesheet" href="static/codemirror/theme/default.css">
35 34
36 35 <link rel="stylesheet" href="static/prettify/prettify.css"/>
37 36
38 37 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
39 38 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
40 39 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
41 40 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
42 41 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
43 42
44 43 <meta name="read_only" content="{{read_only}}"/>
45 44
46 45 </head>
47 46
48 <body onload='CheckMathJax();'
47 <body
49 48 data-project={{project}} data-notebook-id={{notebook_id}}
50 49 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
51 50 >
52 51
53 52 <div id="header">
54 53 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
55 54 <span id="save_widget">
56 55 <input type="text" id="notebook_name" size="20"></textarea>
57 56 <button id="save_notebook"><u>S</u>ave</button>
58 57 </span>
59 58 <span id="quick_help_area">
60 59 <button id="quick_help">Quick<u>H</u>elp</button>
61 60 </span>
62 61
63 62 <span id="login_widget">
64 63 {% comment This is a temporary workaround to hide the logout button %}
65 64 {% comment when appropriate until notebook.html is templated %}
66 65 {% if current_user and current_user != 'anonymous' %}
67 66 <button id="logout">Logout</button>
68 67 {% end %}
69 68 </span>
70 69
71 70 <span id="kernel_status">Idle</span>
72 71 </div>
73 72
74 <div id="MathJaxFetchingWarning"
75 style="width:80%; margin:auto;padding-top:20%;text-align: justify; display:none">
76 <p style="font-size:26px;">There was an issue trying to fetch MathJax.js
77 from the internet.</p>
78
79 <p style="padding:0.2em"> With a working internet connection, you can run
80 the following at a Python or IPython prompt, which will install a local
81 copy of MathJax:</p>
82
83 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
84 from IPython.external import mathjax; mathjax.install_mathjax()
85 </pre>
86 This will try to install MathJax into the directory where you installed
87 IPython. If you installed IPython to a location that requires
88 administrative privileges to write, you will need to make this call as
89 an administrator. On OSX/Linux/Unix, this can be done at the
90 command-line via:
91 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
92 sudo python -c "from IPython.external import mathjax; mathjax.install_mathjax()"
93 </pre>
94 </p>
95 </div>
96
97 73 <div id="main_app">
98 74
99 75 <div id="left_panel">
100 76
101 77 <div id="notebook_section">
102 78 <div class="section_header">
103 79 <h3>Notebook</h3>
104 80 </div>
105 81 <div class="section_content">
106 82 <div class="section_row">
107 83 <span id="new_open" class="section_row_buttons">
108 84 <button id="new_notebook">New</button>
109 85 <button id="open_notebook">Open</button>
110 86 </span>
111 87 <span class="section_row_header">Actions</span>
112 88 </div>
113 89 <div class="section_row">
114 90 <span>
115 91 <select id="download_format">
116 92 <option value="json">ipynb</option>
117 93 <option value="py">py</option>
118 94 </select>
119 95 </span>
120 96 <span class="section_row_buttons">
121 97 <button id="download_notebook">Download</button>
122 98 </span>
123 99 </div>
124 100 <div class="section_row">
125 101 <span class="section_row_buttons">
126 102 <span id="print_widget">
127 103 <button id="print_notebook">Print</button>
128 104 </span>
129 105 </span>
130 106 </div>
131 107 </div>
132 108 </div>
133 109
134 110 <div id="cell_section">
135 111 <div class="section_header">
136 112 <h3>Cell</h3>
137 113 </div>
138 114 <div class="section_content">
139 115 <div class="section_row">
140 116 <span class="section_row_buttons">
141 117 <button id="delete_cell"><u>D</u>elete</button>
142 118 </span>
143 119 <span class="section_row_header">Actions</span>
144 120 </div>
145 121 <div class="section_row">
146 122 <span id="cell_type" class="section_row_buttons">
147 123 <button id="to_code"><u>C</u>ode</button>
148 124 <!-- <button id="to_html">HTML</button>-->
149 125 <button id="to_markdown"><u>M</u>arkdown</button>
150 126 </span>
151 127 <span class="button_label">Format</span>
152 128 </div>
153 129 <div class="section_row">
154 130 <span id="cell_output" class="section_row_buttons">
155 131 <button id="toggle_output"><u>T</u>oggle</button>
156 132 <button id="clear_all_output">ClearAll</button>
157 133 </span>
158 134 <span class="button_label">Output</span>
159 135 </div>
160 136 <div class="section_row">
161 137 <span id="insert" class="section_row_buttons">
162 138 <button id="insert_cell_above"><u>A</u>bove</button>
163 139 <button id="insert_cell_below"><u>B</u>elow</button>
164 140 </span>
165 141 <span class="button_label">Insert</span>
166 142 </div>
167 143 <div class="section_row">
168 144 <span id="move" class="section_row_buttons">
169 145 <button id="move_cell_up">Up</button>
170 146 <button id="move_cell_down">Down</button>
171 147 </span>
172 148 <span class="button_label">Move</span>
173 149 </div>
174 150 <div class="section_row">
175 151 <span id="run_cells" class="section_row_buttons">
176 152 <button id="run_selected_cell">Selected</button>
177 153 <button id="run_all_cells">All</button>
178 154 </span>
179 155 <span class="button_label">Run</span>
180 156 </div>
181 157 <div class="section_row">
182 158 <span id="autoindent_span">
183 159 <input type="checkbox" id="autoindent" checked="true"></input>
184 160 </span>
185 161 <span class="checkbox_label" id="autoindent_label">Autoindent:</span>
186 162 </div>
187 163 </div>
188 164 </div>
189 165
190 166 <div id="kernel_section">
191 167 <div class="section_header">
192 168 <h3>Kernel</h3>
193 169 </div>
194 170 <div class="section_content">
195 171 <div class="section_row">
196 172 <span id="int_restart" class="section_row_buttons">
197 173 <button id="int_kernel"><u>I</u>nterrupt</button>
198 174 <button id="restart_kernel">Restart</button>
199 175 </span>
200 176 <span class="section_row_header">Actions</span>
201 177 </div>
202 178 <div class="section_row">
203 179 <span id="kernel_persist">
204 180 {% if kill_kernel %}
205 181 <input type="checkbox" id="kill_kernel" checked="true"></input>
206 182 {% else %}
207 183 <input type="checkbox" id="kill_kernel"></input>
208 184 {% end %}
209 185 </span>
210 186 <span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
211 187 </div>
212 188 </div>
213 189 </div>
214 190
215 191 <div id="help_section">
216 192 <div class="section_header">
217 193 <h3>Help</h3>
218 194 </div>
219 195 <div class="section_content">
220 196 <div class="section_row">
221 197 <span id="help_buttons0" class="section_row_buttons">
222 198 <a id="python_help" href="http://docs.python.org" target="_blank">Python</a>
223 199 <a id="ipython_help" href="http://ipython.org/documentation.html" target="_blank">IPython</a>
224 200 </span>
225 201 <span class="section_row_header">Links</span>
226 202 </div>
227 203 <div class="section_row">
228 204 <span id="help_buttons1" class="section_row_buttons">
229 205 <a id="numpy_help" href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a>
230 206 <a id="scipy_help" href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a>
231 207 </span>
232 208 </div>
233 209 <div class="section_row">
234 210 <span id="help_buttons2" class="section_row_buttons">
235 211 <a id="matplotlib_help" href="http://matplotlib.sourceforge.net/" target="_blank">MPL</a>
236 212 <a id="sympy_help" href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a>
237 213 </span>
238 214 </div>
239 215 <div class="section_row">
240 216 <span class="help_string">run selected cell</span>
241 217 <span class="help_string_label">Shift-Enter :</span>
242 218 </div>
243 219 <div class="section_row">
244 220 <span class="help_string">run selected cell in-place</span>
245 221 <span class="help_string_label">Ctrl-Enter :</span>
246 222 </div>
247 223 <div class="section_row">
248 224 <span class="help_string">show keyboard shortcuts</span>
249 225 <span class="help_string_label">Ctrl-m h :</span>
250 226 </div>
251 227 </div>
252 228 </div>
253 229
254 230 <div id="config_section">
255 231 <div class="section_header">
256 232 <h3>Config</h3>
257 233 </div>
258 234 <div class="section_content">
259 235 <div class="section_row">
260 236 <span id="tooltipontab_span">
261 237 <input type="checkbox" id="tooltipontab" checked="true"></input>
262 238 </span>
263 239 <span class="checkbox_label" id="tooltipontab_label">Tooltip on tab:</span>
264 240 </div>
265 241 <div class="section_row">
266 242 <span id="smartcompleter_span">
267 243 <input type="checkbox" id="smartcompleter" checked="true"></input>
268 244 </span>
269 245 <span class="checkbox_label" id="smartcompleter_label">Smart completer:</span>
270 246 </div>
271 247 <div class="section_row">
272 248 <span id="timebeforetooltip_span">
273 249 <input type="text" id="timebeforetooltip" value="1200"></input>
274 250 </span>
275 251 <span class="numeric_input_label" id="timebeforetooltip_label">Time before tooltip : </span>
276 252 </div>
277 253 </div>
278 254 </div>
279 255
280 256 </div>
281 257 <div id="left_panel_splitter"></div>
282 258 <div id="notebook_panel">
283 259 <div id="notebook"></div>
284 260 <div id="pager_splitter"></div>
285 261 <div id="pager"></div>
286 262 </div>
287 263
288 264 </div>
289 265
290 266 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
291 267 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
292 268 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
293 269
294 270 <script src="static/codemirror/lib/codemirror.js" charset="utf-8"></script>
295 271 <script src="static/codemirror/mode/python/python.js" charset="utf-8"></script>
296 272 <script src="static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
297 273 <script src="static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
298 274 <script src="static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
299 275 <script src="static/codemirror/mode/css/css.js" charset="utf-8"></script>
300 276 <script src="static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
301 277 <script src="static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
302 278
303 279 <script src="static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
304 280
305 281 <script src="static/prettify/prettify.js" charset="utf-8"></script>
306 282
307 283 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
308 284 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
309 285 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
310 286 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
311 287 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
312 288 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
313 289 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
314 290 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
315 291 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
316 292 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
317 293 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
318 294 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
319 295 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
320 296 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
321 297 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
322 298 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
323 299 <script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
324 300
325 301 </body>
326 302
327 303 </html>
General Comments 0
You need to be logged in to leave comments. Login now