##// END OF EJS Templates
Adding kernel/notebook associations.
Brian E. Granger -
Show More
@@ -1,135 +1,160 b''
1 1 """Tornado handlers for the notebook."""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Imports
5 5 #-----------------------------------------------------------------------------
6 6
7 7 import json
8 8 import logging
9 9 import urllib
10 10
11 11 from tornado import web
12 12 from tornado import websocket
13 13
14 14
15 15 #-----------------------------------------------------------------------------
16 # Handlers
16 # Top-level handlers
17 17 #-----------------------------------------------------------------------------
18 18
19 19
20 20 class NBBrowserHandler(web.RequestHandler):
21 21 def get(self):
22 22 nbm = self.application.notebook_manager
23 23 project = nbm.notebook_dir
24 24 self.render('nbbrowser.html', project=project)
25 25
26 26
27 27 class NewHandler(web.RequestHandler):
28 28 def get(self):
29 29 notebook_id = self.application.notebook_manager.new_notebook()
30 30 self.render('notebook.html', notebook_id=notebook_id)
31 31
32 32
33 33 class NamedNotebookHandler(web.RequestHandler):
34 34 def get(self, notebook_id):
35 35 nbm = self.application.notebook_manager
36 36 if not nbm.notebook_exists(notebook_id):
37 37 raise web.HTTPError(404)
38 38 self.render('notebook.html', notebook_id=notebook_id)
39 39
40 40
41 class KernelHandler(web.RequestHandler):
41 #-----------------------------------------------------------------------------
42 # Kernel handlers
43 #-----------------------------------------------------------------------------
44
45
46 class MainKernelHandler(web.RequestHandler):
42 47
43 48 def get(self):
44 self.finish(json.dumps(self.application.kernel_ids))
49 rkm = self.application.routing_kernel_manager
50 self.finish(json.dumps(rkm.kernel_ids))
45 51
46 52 def post(self):
47 kernel_id = self.application.start_kernel()
53 rkm = self.application.routing_kernel_manager
54 notebook_id = self.get_argument('notebook', default=None)
55 kernel_id = rkm.start_kernel(notebook_id)
48 56 self.set_header('Location', '/'+kernel_id)
49 57 self.finish(json.dumps(kernel_id))
50 58
51 59
60 class KernelHandler(web.RequestHandler):
61
62 SUPPORTED_METHODS = ('DELETE')
63
64 def delete(self, kernel_id):
65 rkm = self.application.routing_kernel_manager
66 self.kill_kernel(kernel_id)
67 self.set_status(204)
68 self.finish()
69
70
52 71 class KernelActionHandler(web.RequestHandler):
53 72
54 73 def post(self, kernel_id, action):
55 # TODO: figure out a better way of handling RPC style calls.
74 rkm = self.application.routing_kernel_manager
56 75 if action == 'interrupt':
57 self.application.interrupt_kernel(kernel_id)
76 rkm.interrupt_kernel(kernel_id)
77 self.set_status(204)
58 78 if action == 'restart':
59 new_kernel_id = self.application.restart_kernel(kernel_id)
79 new_kernel_id = rkm.restart_kernel(kernel_id)
60 80 self.write(json.dumps(new_kernel_id))
61 81 self.finish()
62 82
63 83
64 84 class ZMQStreamHandler(websocket.WebSocketHandler):
65 85
66 86 def initialize(self, stream_name):
67 87 self.stream_name = stream_name
68 88
69 89 def open(self, kernel_id):
70 self.router = self.application.get_router(kernel_id, self.stream_name)
90 rkm = self.application.routing_kernel_manager
91 self.router = rkm.get_router(kernel_id, self.stream_name)
71 92 self.client_id = self.router.register_client(self)
72 93 logging.info("Connection open: %s, %s" % (kernel_id, self.client_id))
73 94
74 95 def on_message(self, msg):
75 96 self.router.forward_msg(self.client_id, msg)
76 97
77 98 def on_close(self):
78 99 self.router.unregister_client(self.client_id)
79 100 logging.info("Connection closed: %s" % self.client_id)
80 101
81 102
103 #-----------------------------------------------------------------------------
104 # Notebook web service handlers
105 #-----------------------------------------------------------------------------
106
82 107 class NotebookRootHandler(web.RequestHandler):
83 108
84 109 def get(self):
85 110 nbm = self.application.notebook_manager
86 111 files = nbm.list_notebooks()
87 112 self.finish(json.dumps(files))
88 113
89 114 def post(self):
90 115 nbm = self.application.notebook_manager
91 116 body = self.request.body.strip()
92 117 format = self.get_argument('format', default='json')
93 118 name = self.get_argument('name', default=None)
94 119 if body:
95 120 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
96 121 else:
97 122 notebook_id = nbm.new_notebook()
98 123 self.set_header('Location', '/'+notebook_id)
99 124 self.finish(json.dumps(notebook_id))
100 125
101 126
102 127 class NotebookHandler(web.RequestHandler):
103 128
104 129 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
105 130
106 131 def get(self, notebook_id):
107 132 nbm = self.application.notebook_manager
108 133 format = self.get_argument('format', default='json')
109 134 last_mod, name, data = nbm.get_notebook(notebook_id, format)
110 135 if format == u'json':
111 136 self.set_header('Content-Type', 'application/json')
112 137 self.set_header('Content-Disposition','attachment; filename=%s.json' % name)
113 138 elif format == u'xml':
114 139 self.set_header('Content-Type', 'application/xml')
115 140 self.set_header('Content-Disposition','attachment; filename=%s.ipynb' % name)
116 141 elif format == u'py':
117 142 self.set_header('Content-Type', 'application/x-python')
118 143 self.set_header('Content-Disposition','attachment; filename=%s.py' % name)
119 144 self.set_header('Last-Modified', last_mod)
120 145 self.finish(data)
121 146
122 147 def put(self, notebook_id):
123 148 nbm = self.application.notebook_manager
124 149 format = self.get_argument('format', default='json')
125 150 name = self.get_argument('name', default=None)
126 151 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
127 152 self.set_status(204)
128 153 self.finish()
129 154
130 155 def delete(self, notebook_id):
131 156 nbm = self.application.notebook_manager
132 157 nbm.delete_notebook(notebook_id)
133 158 self.set_status(204)
134 159 self.finish()
135 160
@@ -1,188 +1,306 b''
1 1 """A kernel manager for multiple kernels."""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING.txt, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 import signal
15 15 import sys
16 16 import uuid
17 17
18 18 import zmq
19 19
20 from tornado import web
21
22 from .routers import IOPubStreamRouter, ShellStreamRouter
23
20 24 from IPython.config.configurable import LoggingConfigurable
21 25 from IPython.zmq.ipkernel import launch_kernel
22 from IPython.utils.traitlets import Instance, Dict
26 from IPython.utils.traitlets import Instance, Dict, List, Unicode
23 27
24 28 #-----------------------------------------------------------------------------
25 29 # Classes
26 30 #-----------------------------------------------------------------------------
27 31
28 32 class DuplicateKernelError(Exception):
29 33 pass
30 34
31 35
32 36 class KernelManager(LoggingConfigurable):
33 37 """A class for managing multiple kernels."""
34 38
35 39 context = Instance('zmq.Context')
36 40 def _context_default(self):
37 41 return zmq.Context.instance()
38 42
39 43 _kernels = Dict()
40 44
41 45 @property
42 46 def kernel_ids(self):
43 47 """Return a list of the kernel ids of the active kernels."""
44 48 return self._kernels.keys()
45 49
46 50 def __len__(self):
47 51 """Return the number of running kernels."""
48 52 return len(self.kernel_ids)
49 53
50 54 def __contains__(self, kernel_id):
51 55 if kernel_id in self.kernel_ids:
52 56 return True
53 57 else:
54 58 return False
55 59
56 60 def start_kernel(self, **kwargs):
57 61 """Start a new kernel."""
58 kernel_id = str(uuid.uuid4())
62 kernel_id = unicode(uuid.uuid4())
59 63 (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel(**kwargs)
60 64 # Store the information for contacting the kernel. This assumes the kernel is
61 65 # running on localhost.
62 66 d = dict(
63 67 process = process,
64 68 stdin_port = stdin_port,
65 69 iopub_port = iopub_port,
66 70 shell_port = shell_port,
67 71 hb_port = hb_port,
68 72 ip = '127.0.0.1'
69 73 )
70 74 self._kernels[kernel_id] = d
71 75 return kernel_id
72 76
73 77 def kill_kernel(self, kernel_id):
74 78 """Kill a kernel by its kernel uuid.
75 79
76 80 Parameters
77 81 ==========
78 82 kernel_id : uuid
79 83 The id of the kernel to kill.
80 84 """
81 85 kernel_process = self.get_kernel_process(kernel_id)
82 86 if kernel_process is not None:
83 87 # Attempt to kill the kernel.
84 88 try:
85 89 kernel_process.kill()
86 90 except OSError, e:
87 91 # In Windows, we will get an Access Denied error if the process
88 92 # has already terminated. Ignore it.
89 93 if not (sys.platform == 'win32' and e.winerror == 5):
90 94 raise
91 95 del self._kernels[kernel_id]
92 96
93 97 def interrupt_kernel(self, kernel_id):
94 98 """Interrupt (SIGINT) the kernel by its uuid.
95 99
96 100 Parameters
97 101 ==========
98 102 kernel_id : uuid
99 103 The id of the kernel to interrupt.
100 104 """
101 105 kernel_process = self.get_kernel_process(kernel_id)
102 106 if kernel_process is not None:
103 107 if sys.platform == 'win32':
104 108 from parentpoller import ParentPollerWindows as Poller
105 109 Poller.send_interrupt(kernel_process.win32_interrupt_event)
106 110 else:
107 111 kernel_process.send_signal(signal.SIGINT)
108 112
109 113 def signal_kernel(self, kernel_id, signum):
110 114 """ Sends a signal to the kernel by its uuid.
111 115
112 116 Note that since only SIGTERM is supported on Windows, this function
113 117 is only useful on Unix systems.
114 118
115 119 Parameters
116 120 ==========
117 121 kernel_id : uuid
118 122 The id of the kernel to signal.
119 123 """
120 124 kernel_process = self.get_kernel_process(kernel_id)
121 125 if kernel_process is not None:
122 126 kernel_process.send_signal(signum)
123 127
124 128 def get_kernel_process(self, kernel_id):
125 129 """Get the process object for a kernel by its uuid.
126 130
127 131 Parameters
128 132 ==========
129 133 kernel_id : uuid
130 134 The id of the kernel.
131 135 """
132 136 d = self._kernels.get(kernel_id)
133 137 if d is not None:
134 138 return d['process']
135 139 else:
136 140 raise KeyError("Kernel with id not found: %s" % kernel_id)
137 141
138 142 def get_kernel_ports(self, kernel_id):
139 143 """Return a dictionary of ports for a kernel.
140 144
141 145 Parameters
142 146 ==========
143 147 kernel_id : uuid
144 148 The id of the kernel.
145 149
146 150 Returns
147 151 =======
148 152 port_dict : dict
149 153 A dict of key, value pairs where the keys are the names
150 154 (stdin_port,iopub_port,shell_port) and the values are the
151 155 integer port numbers for those channels.
152 156 """
153 157 d = self._kernels.get(kernel_id)
154 158 if d is not None:
155 159 dcopy = d.copy()
156 160 dcopy.pop('process')
157 161 dcopy.pop('ip')
158 162 return dcopy
159 163 else:
160 164 raise KeyError("Kernel with id not found: %s" % kernel_id)
161 165
162 166 def get_kernel_ip(self, kernel_id):
163 167 """Return ip address for a kernel.
164 168
165 169 Parameters
166 170 ==========
167 171 kernel_id : uuid
168 172 The id of the kernel.
169 173
170 174 Returns
171 175 =======
172 176 ip : str
173 177 The ip address of the kernel.
174 178 """
175 179 d = self._kernels.get(kernel_id)
176 180 if d is not None:
177 181 return d['ip']
178 182 else:
179 183 raise KeyError("Kernel with id not found: %s" % kernel_id)
180 184
181 185 def create_session_manager(self, kernel_id):
182 186 """Create a new session manager for a kernel by its uuid."""
183 187 from sessionmanager import SessionManager
184 188 return SessionManager(
185 189 kernel_id=kernel_id, kernel_manager=self,
186 190 config=self.config, context=self.context, log=self.log
187 191 )
188 192
193
194 class RoutingKernelManager(LoggingConfigurable):
195 """A KernelManager that handles WebSocket routing and HTTP error handling"""
196
197 kernel_argv = List(Unicode)
198 kernel_manager = Instance(KernelManager)
199
200 _routers = Dict()
201 _session_dict = Dict()
202 _notebook_mapping = Dict()
203
204 #-------------------------------------------------------------------------
205 # Methods for managing kernels and sessions
206 #-------------------------------------------------------------------------
207
208 @property
209 def kernel_ids(self):
210 return self.kernel_manager.kernel_ids
211
212 def notebook_for_kernel(self, kernel_id):
213 notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
214 if len(notebook_ids) == 1:
215 return notebook_ids[0]
216 else:
217 return None
218
219 def delete_mapping_for_kernel(self, kernel_id):
220 notebook_id = self.notebook_for_kernel(kernel_id)
221 if notebook_id is not None:
222 del self._notebook_mapping[notebook_id]
223
224 def start_kernel(self, notebook_id=None):
225 self.log.info
226 kernel_id = self._notebook_mapping.get(notebook_id)
227 if kernel_id is None:
228 kwargs = dict()
229 kwargs['extra_arguments'] = self.kernel_argv
230 kernel_id = self.kernel_manager.start_kernel(**kwargs)
231 if notebook_id is not None:
232 self._notebook_mapping[notebook_id] = kernel_id
233 self.log.info("Kernel started for notebook %s: %s" % (notebook_id,kernel_id))
234 self.log.debug("Kernel args: %r" % kwargs)
235 self.start_session_manager(kernel_id)
236 else:
237 self.log.info("Using existing kernel: %s" % kernel_id)
238 return kernel_id
239
240 def start_session_manager(self, kernel_id):
241 sm = self.kernel_manager.create_session_manager(kernel_id)
242 self._session_dict[kernel_id] = sm
243 iopub_stream = sm.get_iopub_stream()
244 shell_stream = sm.get_shell_stream()
245 iopub_router = IOPubStreamRouter(
246 zmq_stream=iopub_stream, session=sm.session, config=self.config
247 )
248 shell_router = ShellStreamRouter(
249 zmq_stream=shell_stream, session=sm.session, config=self.config
250 )
251 self._routers[(kernel_id, 'iopub')] = iopub_router
252 self._routers[(kernel_id, 'shell')] = shell_router
253
254 def kill_kernel(self, kernel_id):
255 if kernel_id not in self.kernel_manager:
256 raise web.HTTPError(404)
257 try:
258 sm = self._session_dict.pop(kernel_id)
259 except KeyError:
260 raise web.HTTPError(404)
261 sm.stop()
262 self.kernel_manager.kill_kernel(kernel_id)
263 self.delete_mapping_for_kernel(kernel_id)
264 self.log.info("Kernel killed: %s" % kernel_id)
265
266 def interrupt_kernel(self, kernel_id):
267 if kernel_id not in self.kernel_manager:
268 raise web.HTTPError(404)
269 self.kernel_manager.interrupt_kernel(kernel_id)
270 self.log.debug("Kernel interrupted: %s" % kernel_id)
271
272 def restart_kernel(self, kernel_id):
273 if kernel_id not in self.kernel_manager:
274 raise web.HTTPError(404)
275
276 # Get the notebook_id to preserve the kernel/notebook association
277 notebook_id = self.notebook_for_kernel(kernel_id)
278 # Create the new kernel first so we can move the clients over.
279 new_kernel_id = self.start_kernel()
280
281 # Copy the clients over to the new routers.
282 old_iopub_router = self.get_router(kernel_id, 'iopub')
283 old_shell_router = self.get_router(kernel_id, 'shell')
284 new_iopub_router = self.get_router(new_kernel_id, 'iopub')
285 new_shell_router = self.get_router(new_kernel_id, 'shell')
286 new_iopub_router.copy_clients(old_iopub_router)
287 new_shell_router.copy_clients(old_shell_router)
288
289 # Now shutdown the old session and the kernel.
290 # TODO: This causes a hard crash in ZMQStream.close, which sets
291 # self.socket to None to hastily. We will need to fix this in PyZMQ
292 # itself. For now, we just leave the old kernel running :(
293 # Maybe this is fixed now, but nothing was changed really.
294 self.kill_kernel(kernel_id)
295
296 # Now save the new kernel/notebook association. We have to save it
297 # after the old kernel is killed as that will delete the mapping.
298 self._notebook_mapping[notebook_id] = kernel_id
299
300 self.log.debug("Kernel restarted: %s -> %s" % (kernel_id, new_kernel_id))
301 return new_kernel_id
302
303 def get_router(self, kernel_id, stream_name):
304 router = self._routers[(kernel_id, stream_name)]
305 return router
306
@@ -1,270 +1,203 b''
1 1 """A tornado based IPython notebook server."""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING.txt, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 import logging
15 15 import os
16 16 import signal
17 17 import sys
18 18
19 19 import zmq
20 20
21 21 # Install the pyzmq ioloop. This has to be done before anything else from
22 22 # tornado is imported.
23 23 from zmq.eventloop import ioloop
24 24 import tornado.ioloop
25 25 tornado.ioloop = ioloop
26 26
27 27 from tornado import httpserver
28 28 from tornado import web
29 29
30 from .kernelmanager import KernelManager
30 from .kernelmanager import KernelManager, RoutingKernelManager
31 31 from .sessionmanager import SessionManager
32 32 from .handlers import (
33 33 NBBrowserHandler, NewHandler, NamedNotebookHandler,
34 KernelHandler, KernelActionHandler, ZMQStreamHandler,
34 MainKernelHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler,
35 35 NotebookRootHandler, NotebookHandler
36 36 )
37 from .routers import IOPubStreamRouter, ShellStreamRouter
38 37 from .notebookmanager import NotebookManager
39 38
40 39 from IPython.core.application import BaseIPythonApplication
41 40 from IPython.core.profiledir import ProfileDir
42 41 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
43 42 from IPython.zmq.session import Session
44 43 from IPython.zmq.zmqshell import ZMQInteractiveShell
45 44 from IPython.zmq.ipkernel import (
46 45 flags as ipkernel_flags,
47 46 aliases as ipkernel_aliases,
48 47 IPKernelApp
49 48 )
50 49 from IPython.utils.traitlets import Dict, Unicode, Int, Any, List, Enum
51 50
52 51 #-----------------------------------------------------------------------------
53 52 # Module globals
54 53 #-----------------------------------------------------------------------------
55 54
56 55 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
57 56 _kernel_action_regex = r"(?P<action>restart|interrupt)"
58 57 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
59 58
60 59 LOCALHOST = '127.0.0.1'
61 60
62 61 #-----------------------------------------------------------------------------
63 62 # The Tornado web application
64 63 #-----------------------------------------------------------------------------
65 64
66 65 class NotebookWebApplication(web.Application):
67 66
68 def __init__(self, kernel_manager, log, kernel_argv, config):
67 def __init__(self, routing_kernel_manager, notebook_manager, log):
69 68 handlers = [
70 69 (r"/", NBBrowserHandler),
71 70 (r"/new", NewHandler),
72 71 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
73 (r"/kernels", KernelHandler),
72 (r"/kernels", MainKernelHandler),
73 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
74 74 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
75 75 (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')),
76 76 (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')),
77 77 (r"/notebooks", NotebookRootHandler),
78 78 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler)
79 79 ]
80 80 settings = dict(
81 81 template_path=os.path.join(os.path.dirname(__file__), "templates"),
82 82 static_path=os.path.join(os.path.dirname(__file__), "static"),
83 83 )
84 84 web.Application.__init__(self, handlers, **settings)
85 85
86 self.kernel_manager = kernel_manager
86 self.routing_kernel_manager = routing_kernel_manager
87 87 self.log = log
88 self.kernel_argv = kernel_argv
89 self.config = config
90 self._routers = {}
91 self._session_dict = {}
92 self.notebook_manager = NotebookManager(config=self.config)
93
94 #-------------------------------------------------------------------------
95 # Methods for managing kernels and sessions
96 #-------------------------------------------------------------------------
97
98 @property
99 def kernel_ids(self):
100 return self.kernel_manager.kernel_ids
101
102 def start_kernel(self):
103 kwargs = dict()
104 kwargs['extra_arguments'] = self.kernel_argv
105 kernel_id = self.kernel_manager.start_kernel(**kwargs)
106 self.log.info("Kernel started: %s" % kernel_id)
107 self.log.debug("Kernel args: %r" % kwargs)
108 self.start_session_manager(kernel_id)
109 return kernel_id
110
111 def start_session_manager(self, kernel_id):
112 sm = self.kernel_manager.create_session_manager(kernel_id)
113 self._session_dict[kernel_id] = sm
114 iopub_stream = sm.get_iopub_stream()
115 shell_stream = sm.get_shell_stream()
116 iopub_router = IOPubStreamRouter(
117 zmq_stream=iopub_stream, session=sm.session, config=self.config
118 )
119 shell_router = ShellStreamRouter(
120 zmq_stream=shell_stream, session=sm.session, config=self.config
121 )
122 self._routers[(kernel_id, 'iopub')] = iopub_router
123 self._routers[(kernel_id, 'shell')] = shell_router
124
125 def kill_kernel(self, kernel_id):
126 sm = self._session_dict.pop(kernel_id)
127 sm.stop()
128 self.kernel_manager.kill_kernel(kernel_id)
129 self.log.info("Kernel killed: %s" % kernel_id)
130
131 def interrupt_kernel(self, kernel_id):
132 self.kernel_manager.interrupt_kernel(kernel_id)
133 self.log.debug("Kernel interrupted: %s" % kernel_id)
134
135 def restart_kernel(self, kernel_id):
136 # Create the new kernel first so we can move the clients over.
137 new_kernel_id = self.start_kernel()
138
139 # Copy the clients over to the new routers.
140 old_iopub_router = self.get_router(kernel_id, 'iopub')
141 old_shell_router = self.get_router(kernel_id, 'shell')
142 new_iopub_router = self.get_router(new_kernel_id, 'iopub')
143 new_shell_router = self.get_router(new_kernel_id, 'shell')
144 new_iopub_router.copy_clients(old_iopub_router)
145 new_shell_router.copy_clients(old_shell_router)
146
147 # Now shutdown the old session and the kernel.
148 # TODO: This causes a hard crash in ZMQStream.close, which sets
149 # self.socket to None to hastily. We will need to fix this in PyZMQ
150 # itself. For now, we just leave the old kernel running :(
151 # Maybe this is fixed now, but nothing was changed really.
152 self.kill_kernel(kernel_id)
153
154 self.log.debug("Kernel restarted: %s -> %s" % (kernel_id, new_kernel_id))
155 return new_kernel_id
156
157 def get_router(self, kernel_id, stream_name):
158 router = self._routers[(kernel_id, stream_name)]
159 return router
160
88 self.notebook_manager = notebook_manager
161 89
162 90
163 91 #-----------------------------------------------------------------------------
164 92 # Aliases and Flags
165 93 #-----------------------------------------------------------------------------
166 94
167 95 flags = dict(ipkernel_flags)
168 96
169 97 # the flags that are specific to the frontend
170 98 # these must be scrubbed before being passed to the kernel,
171 99 # or it will raise an error on unrecognized flags
172 100 notebook_flags = []
173 101
174 102 aliases = dict(ipkernel_aliases)
175 103
176 104 aliases.update(dict(
177 105 ip = 'IPythonNotebookApp.ip',
178 106 port = 'IPythonNotebookApp.port',
179 107 colors = 'ZMQInteractiveShell.colors',
180 108 editor = 'RichIPythonWidget.editor',
181 109 ))
182 110
183 111 #-----------------------------------------------------------------------------
184 112 # IPythonNotebookApp
185 113 #-----------------------------------------------------------------------------
186 114
187 115 class IPythonNotebookApp(BaseIPythonApplication):
188 116 name = 'ipython-notebook'
189 117 default_config_file_name='ipython_notebook_config.py'
190 118
191 119 description = """
192 120 The IPython HTML Notebook.
193 121
194 122 This launches a Tornado based HTML Notebook Server that serves up an
195 123 HTML5/Javascript Notebook client.
196 124 """
197 125
198 126 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
127 RoutingKernelManager, NotebookManager,
199 128 KernelManager, SessionManager, RichIPythonWidget]
200 129 flags = Dict(flags)
201 130 aliases = Dict(aliases)
202 131
203 132 kernel_argv = List(Unicode)
204 133
205 134 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
206 135 default_value=logging.INFO,
207 136 config=True,
208 137 help="Set the log level by value or name.")
209 138
210 139 # connection info:
211 140 ip = Unicode(LOCALHOST, config=True,
212 141 help="The IP address the notebook server will listen on."
213 142 )
214 143
215 144 port = Int(8888, config=True,
216 145 help="The port the notebook server will listen on."
217 146 )
218 147
219 148 # the factory for creating a widget
220 149 widget_factory = Any(RichIPythonWidget)
221 150
222 151 def parse_command_line(self, argv=None):
223 152 super(IPythonNotebookApp, self).parse_command_line(argv)
224 153 if argv is None:
225 154 argv = sys.argv[1:]
226 155
227 156 self.kernel_argv = list(argv) # copy
228 157 # kernel should inherit default config file from frontend
229 158 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
230 159 # scrub frontend-specific flags
231 160 for a in argv:
232 161 if a.startswith('-') and a.lstrip('-') in notebook_flags:
233 162 self.kernel_argv.remove(a)
234 163
235 def init_kernel_manager(self):
164 def init_configurables(self):
236 165 # Don't let Qt or ZMQ swallow KeyboardInterupts.
237 166 signal.signal(signal.SIGINT, signal.SIG_DFL)
238 167
239 168 # Create a KernelManager and start a kernel.
240 169 self.kernel_manager = KernelManager(config=self.config, log=self.log)
170 self.routing_kernel_manager = RoutingKernelManager(config=self.config, log=self.log,
171 kernel_manager=self.kernel_manager, kernel_argv=self.kernel_argv
172 )
173 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
241 174
242 175 def init_logging(self):
243 176 super(IPythonNotebookApp, self).init_logging()
244 177 # This prevents double log messages because tornado use a root logger that
245 178 # self.log is a child of. The logging module dipatches log messages to a log
246 179 # and all of its ancenstors until propagate is set to False.
247 180 self.log.propagate = False
248 181
249 182 def initialize(self, argv=None):
250 183 super(IPythonNotebookApp, self).initialize(argv)
251 self.init_kernel_manager()
184 self.init_configurables()
252 185 self.web_app = NotebookWebApplication(
253 self.kernel_manager, self.log, self.kernel_argv, self.config
186 self.routing_kernel_manager, self.notebook_manager, self.log
254 187 )
255 188 self.http_server = httpserver.HTTPServer(self.web_app)
256 189 self.http_server.listen(self.port)
257 190
258 191 def start(self):
259 192 self.log.info("The IPython Notebook is running at: http://%s:%i" % (self.ip, self.port))
260 193 ioloop.IOLoop.instance().start()
261 194
262 195 #-----------------------------------------------------------------------------
263 196 # Main entry point
264 197 #-----------------------------------------------------------------------------
265 198
266 199 def launch_new_instance():
267 200 app = IPythonNotebookApp()
268 201 app.initialize()
269 202 app.start()
270 203
@@ -1,225 +1,225 b''
1 1 #-----------------------------------------------------------------------------
2 2 # Copyright (C) 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.txt, distributed as part of this software.
6 6 #-----------------------------------------------------------------------------
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Imports
10 10 #-----------------------------------------------------------------------------
11 11
12 12 import datetime
13 13 import os
14 14 import uuid
15 15
16 16 from tornado import web
17 17
18 from IPython.config.configurable import Configurable
18 from IPython.config.configurable import LoggingConfigurable
19 19 from IPython.nbformat import current
20 20 from IPython.utils.traitlets import Unicode, List, Dict
21 21
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Code
25 25 #-----------------------------------------------------------------------------
26 26
27 27
28 class NotebookManager(Configurable):
28 class NotebookManager(LoggingConfigurable):
29 29
30 30 notebook_dir = Unicode(os.getcwd())
31 31 filename_ext = Unicode(u'.ipynb')
32 32 allowed_formats = List([u'json',u'xml',u'py'])
33 33
34 34 # Map notebook_ids to notebook names
35 35 mapping = Dict()
36 36 # Map notebook names to notebook_ids
37 37 rev_mapping = Dict()
38 38
39 39 def list_notebooks(self):
40 40 """List all notebooks in the notebook dir.
41 41
42 42 This returns a list of dicts of the form::
43 43
44 44 dict(notebook_id=notebook,name=name)
45 45 """
46 46 names = os.listdir(self.notebook_dir)
47 47 names = [name.split(u'.')[0] \
48 48 for name in names if name.endswith(self.filename_ext)]
49 49 data = []
50 50 for name in names:
51 51 if name not in self.rev_mapping:
52 52 notebook_id = self.new_notebook_id(name)
53 53 else:
54 54 notebook_id = self.rev_mapping[name]
55 55 data.append(dict(notebook_id=notebook_id,name=name))
56 56 data = sorted(data, key=lambda item: item['name'])
57 57 return data
58 58
59 59 def new_notebook_id(self, name):
60 60 """Generate a new notebook_id for a name and store its mappings."""
61 61 notebook_id = unicode(uuid.uuid4())
62 62 self.mapping[notebook_id] = name
63 63 self.rev_mapping[name] = notebook_id
64 64 return notebook_id
65 65
66 66 def delete_notebook_id(self, notebook_id):
67 67 """Delete a notebook's id only. This doesn't delete the actual notebook."""
68 68 name = self.mapping[notebook_id]
69 69 del self.mapping[notebook_id]
70 70 del self.rev_mapping[name]
71 71
72 72 def notebook_exists(self, notebook_id):
73 73 """Does a notebook exist?"""
74 74 if notebook_id not in self.mapping:
75 75 return False
76 76 path = self.get_path_by_name(self.mapping[notebook_id])
77 77 if not os.path.isfile(path):
78 78 return False
79 79 return True
80 80
81 81 def find_path(self, notebook_id):
82 82 """Return a full path to a notebook given its notebook_id."""
83 83 try:
84 84 name = self.mapping[notebook_id]
85 85 except KeyError:
86 86 raise web.HTTPError(404)
87 87 return self.get_path_by_name(name)
88 88
89 89 def get_path_by_name(self, name):
90 90 """Return a full path to a notebook given its name."""
91 91 filename = name + self.filename_ext
92 92 path = os.path.join(self.notebook_dir, filename)
93 93 return path
94 94
95 95 def get_notebook(self, notebook_id, format=u'json'):
96 96 """Get the representation of a notebook in format by notebook_id."""
97 97 format = unicode(format)
98 98 if format not in self.allowed_formats:
99 99 raise web.HTTPError(415)
100 100 last_modified, nb = self.get_notebook_object(notebook_id)
101 101 data = current.writes(nb, format)
102 102 name = nb.get('name','notebook')
103 103 return last_modified, name, data
104 104
105 105 def get_notebook_object(self, notebook_id):
106 106 """Get the NotebookNode representation of a notebook by notebook_id."""
107 107 path = self.find_path(notebook_id)
108 108 if not os.path.isfile(path):
109 109 raise web.HTTPError(404)
110 110 info = os.stat(path)
111 111 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
112 112 try:
113 113 with open(path,'r') as f:
114 114 s = f.read()
115 115 try:
116 116 # v2 and later have xml in the .ipynb files.
117 117 nb = current.reads(s, 'xml')
118 118 except:
119 119 # v1 had json in the .ipynb files.
120 120 nb = current.reads(s, 'json')
121 121 # v1 notebooks don't have a name field, so use the filename.
122 122 nb.name = os.path.split(path)[-1].split(u'.')[0]
123 123 except:
124 124 raise web.HTTPError(404)
125 125 return last_modified, nb
126 126
127 127 def save_new_notebook(self, data, name=None, format=u'json'):
128 128 """Save a new notebook and return its notebook_id.
129 129
130 130 If a name is passed in, it overrides any values in the notebook data
131 131 and the value in the data is updated to use that value.
132 132 """
133 133 if format not in self.allowed_formats:
134 134 raise web.HTTPError(415)
135 135
136 136 try:
137 137 nb = current.reads(data, format)
138 138 except:
139 139 if format == u'xml':
140 140 # v1 notebooks might come in with a format='xml' but be json.
141 141 try:
142 142 nb = current.reads(data, u'json')
143 143 except:
144 144 raise web.HTTPError(400)
145 145 else:
146 146 raise web.HTTPError(400)
147 147
148 148 if name is None:
149 149 try:
150 150 name = nb.name
151 151 except AttributeError:
152 152 raise web.HTTPError(400)
153 153 nb.name = name
154 154
155 155 notebook_id = self.new_notebook_id(name)
156 156 self.save_notebook_object(notebook_id, nb)
157 157 return notebook_id
158 158
159 159 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
160 160 """Save an existing notebook by notebook_id."""
161 161 if format not in self.allowed_formats:
162 162 raise web.HTTPError(415)
163 163
164 164 try:
165 165 nb = current.reads(data, format)
166 166 except:
167 167 if format == u'xml':
168 168 # v1 notebooks might come in with a format='xml' but be json.
169 169 try:
170 170 nb = current.reads(data, u'json')
171 171 except:
172 172 raise web.HTTPError(400)
173 173 else:
174 174 raise web.HTTPError(400)
175 175
176 176 if name is not None:
177 177 nb.name = name
178 178 self.save_notebook_object(notebook_id, nb)
179 179
180 180 def save_notebook_object(self, notebook_id, nb):
181 181 """Save an existing notebook object by notebook_id."""
182 182 if notebook_id not in self.mapping:
183 183 raise web.HTTPError(404)
184 184 old_name = self.mapping[notebook_id]
185 185 try:
186 186 new_name = nb.name
187 187 except AttributeError:
188 188 raise web.HTTPError(400)
189 189 path = self.get_path_by_name(new_name)
190 190 try:
191 191 with open(path,'w') as f:
192 192 current.write(nb, f, u'xml')
193 193 except:
194 194 raise web.HTTPError(400)
195 195 if old_name != new_name:
196 196 old_path = self.get_path_by_name(old_name)
197 197 if os.path.isfile(old_path):
198 198 os.unlink(old_path)
199 199 self.mapping[notebook_id] = new_name
200 200 self.rev_mapping[new_name] = notebook_id
201 201
202 202 def delete_notebook(self, notebook_id):
203 203 """Delete notebook by notebook_id."""
204 204 path = self.find_path(notebook_id)
205 205 if not os.path.isfile(path):
206 206 raise web.HTTPError(404)
207 207 os.unlink(path)
208 208 self.delete_notebook_id(notebook_id)
209 209
210 210 def new_notebook(self):
211 211 """Create a new notebook and returns its notebook_id."""
212 212 i = 0
213 213 while True:
214 214 name = u'Untitled%i' % i
215 215 path = self.get_path_by_name(name)
216 216 if not os.path.isfile(path):
217 217 break
218 218 else:
219 219 i = i+1
220 220 notebook_id = self.new_notebook_id(name)
221 221 nb = current.new_notebook(name=name, id=notebook_id)
222 222 with open(path,'w') as f:
223 223 current.write(nb, f, u'xml')
224 224 return notebook_id
225 225
@@ -1,105 +1,106 b''
1 1
2 2 //============================================================================
3 3 // Kernel
4 4 //============================================================================
5 5
6 6 var IPython = (function (IPython) {
7 7
8 8 var utils = IPython.utils;
9 9
10 10 var Kernel = function () {
11 11 this.kernel_id = null;
12 12 this.base_url = "/kernels";
13 13 this.kernel_url = null;
14 14 };
15 15
16 16
17 17 Kernel.prototype.get_msg = function (msg_type, content) {
18 18 var msg = {
19 19 header : {
20 20 msg_id : utils.uuid(),
21 21 username : "username",
22 22 session: this.session_id,
23 23 msg_type : msg_type
24 24 },
25 25 content : content,
26 26 parent_header : {}
27 27 };
28 28 return msg;
29 29 }
30 30
31 Kernel.prototype.start_kernel = function (callback) {
31 Kernel.prototype.start_kernel = function (notebook_id, callback) {
32 32 var that = this;
33 $.post(this.base_url,
33 var qs = $.param({notebook:notebook_id});
34 $.post(this.base_url + '?' + qs,
34 35 function (kernel_id) {
35 36 that._handle_start_kernel(kernel_id, callback);
36 37 },
37 38 'json'
38 39 );
39 40 };
40 41
41 42
42 43 Kernel.prototype._handle_start_kernel = function (kernel_id, callback) {
43 44 this.kernel_id = kernel_id;
44 45 this.kernel_url = this.base_url + "/" + this.kernel_id;
45 46 this._start_channels();
46 47 callback();
47 48 };
48 49
49 50
50 51 Kernel.prototype._start_channels = function () {
51 52 var ws_url = "ws://127.0.0.1:8888" + this.kernel_url;
52 53 this.shell_channel = new WebSocket(ws_url + "/shell");
53 54 this.iopub_channel = new WebSocket(ws_url + "/iopub");
54 55 }
55 56
56 57
57 58 Kernel.prototype.execute = function (code) {
58 59 var content = {
59 60 code : code,
60 61 silent : false,
61 62 user_variables : [],
62 63 user_expressions : {}
63 64 };
64 65 var msg = this.get_msg("execute_request", content);
65 66 this.shell_channel.send(JSON.stringify(msg));
66 67 return msg.header.msg_id;
67 68 }
68 69
69 70
70 71 Kernel.prototype.complete = function (line, cursor_pos) {
71 72 var content = {
72 73 text : '',
73 74 line : line,
74 75 cursor_pos : cursor_pos
75 76 };
76 77 var msg = this.get_msg("complete_request", content);
77 78 this.shell_channel.send(JSON.stringify(msg));
78 79 return msg.header.msg_id;
79 80 }
80 81
81 82
82 83 Kernel.prototype.interrupt = function () {
83 84 $.post(this.kernel_url + "/interrupt");
84 85 };
85 86
86 87
87 88 Kernel.prototype.restart = function () {
88 89 IPython.kernel_status_widget.status_restarting();
89 90 url = this.kernel_url + "/restart"
90 91 var that = this;
91 92 $.post(url, function (kernel_id) {
92 93 console.log("Kernel restarted: " + kernel_id);
93 94 that.kernel_id = kernel_id;
94 95 that.kernel_url = that.base_url + "/" + that.kernel_id;
95 96 IPython.kernel_status_widget.status_idle();
96 97 }, 'json');
97 98 };
98 99
99 100
100 101 IPython.Kernel = Kernel;
101 102
102 103 return IPython;
103 104
104 105 }(IPython));
105 106
@@ -1,622 +1,623 b''
1 1
2 2 //============================================================================
3 3 // Notebook
4 4 //============================================================================
5 5
6 6 var IPython = (function (IPython) {
7 7
8 8 var utils = IPython.utils;
9 9
10 10 var Notebook = function (selector) {
11 11 this.element = $(selector);
12 12 this.element.scroll();
13 13 this.element.data("notebook", this);
14 14 this.next_prompt_number = 1;
15 15 this.kernel = null;
16 16 this.msg_cell_map = {};
17 17 this.style();
18 18 this.create_elements();
19 19 this.bind_events();
20 20 };
21 21
22 22
23 23 Notebook.prototype.style = function () {
24 24 $('div#notebook').addClass('border-box-sizing');
25 25 };
26 26
27 27
28 28 Notebook.prototype.create_elements = function () {
29 29 // We add this end_space div to the end of the notebook div to:
30 30 // i) provide a margin between the last cell and the end of the notebook
31 31 // ii) to prevent the div from scrolling up when the last cell is being
32 32 // edited, but is too low on the page, which browsers will do automatically.
33 33 this.element.append($('<div class="end_space"></div>').height(50));
34 34 $('div#notebook').addClass('border-box-sizing');
35 35 };
36 36
37 37
38 38 Notebook.prototype.bind_events = function () {
39 39 var that = this;
40 40 $(document).keydown(function (event) {
41 41 // console.log(event);
42 42 if (event.which === 38) {
43 43 var cell = that.selected_cell();
44 44 if (cell.at_top()) {
45 45 event.preventDefault();
46 46 that.select_prev();
47 47 };
48 48 } else if (event.which === 40) {
49 49 var cell = that.selected_cell();
50 50 if (cell.at_bottom()) {
51 51 event.preventDefault();
52 52 that.select_next();
53 53 };
54 54 } else if (event.which === 13 && event.shiftKey) {
55 55 that.execute_selected_cell();
56 56 return false;
57 57 } else if (event.which === 13 && event.ctrlKey) {
58 58 that.execute_selected_cell({terminal:true});
59 59 return false;
60 60 };
61 61 });
62 62
63 63 this.element.bind('collapse_pager', function () {
64 64 var app_height = $('div#main_app').height(); // content height
65 65 var splitter_height = $('div#pager_splitter').outerHeight(true);
66 66 var new_height = app_height - splitter_height;
67 67 that.element.animate({height : new_height + 'px'}, 'fast');
68 68 });
69 69
70 70 this.element.bind('expand_pager', function () {
71 71 var app_height = $('div#main_app').height(); // content height
72 72 var splitter_height = $('div#pager_splitter').outerHeight(true);
73 73 var pager_height = $('div#pager').outerHeight(true);
74 74 var new_height = app_height - pager_height - splitter_height;
75 75 that.element.animate({height : new_height + 'px'}, 'fast');
76 76 });
77 77
78 78 this.element.bind('collapse_left_panel', function () {
79 79 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
80 80 var new_margin = splitter_width;
81 81 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
82 82 });
83 83
84 84 this.element.bind('expand_left_panel', function () {
85 85 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
86 86 var left_panel_width = IPython.left_panel.width;
87 87 var new_margin = splitter_width + left_panel_width;
88 88 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
89 89 });
90 90 };
91 91
92 92
93 93 Notebook.prototype.scroll_to_bottom = function () {
94 94 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
95 95 };
96 96
97 97
98 98 Notebook.prototype.scroll_to_top = function () {
99 99 this.element.animate({scrollTop:0}, 0);
100 100 };
101 101
102 102
103 103 // Cell indexing, retrieval, etc.
104 104
105 105
106 106 Notebook.prototype.cell_elements = function () {
107 107 return this.element.children("div.cell");
108 108 }
109 109
110 110
111 111 Notebook.prototype.ncells = function (cell) {
112 112 return this.cell_elements().length;
113 113 }
114 114
115 115
116 116 // TODO: we are often calling cells as cells()[i], which we should optimize
117 117 // to cells(i) or a new method.
118 118 Notebook.prototype.cells = function () {
119 119 return this.cell_elements().toArray().map(function (e) {
120 120 return $(e).data("cell");
121 121 });
122 122 }
123 123
124 124
125 125 Notebook.prototype.find_cell_index = function (cell) {
126 126 var result = null;
127 127 this.cell_elements().filter(function (index) {
128 128 if ($(this).data("cell") === cell) {
129 129 result = index;
130 130 };
131 131 });
132 132 return result;
133 133 };
134 134
135 135
136 136 Notebook.prototype.index_or_selected = function (index) {
137 137 return index || this.selected_index() || 0;
138 138 }
139 139
140 140
141 141 Notebook.prototype.select = function (index) {
142 142 if (index !== undefined && index >= 0 && index < this.ncells()) {
143 143 if (this.selected_index() !== null) {
144 144 this.selected_cell().unselect();
145 145 };
146 146 this.cells()[index].select();
147 147 if (index === (this.ncells()-1)) {
148 148 this.scroll_to_bottom();
149 149 };
150 150 };
151 151 return this;
152 152 };
153 153
154 154
155 155 Notebook.prototype.select_next = function () {
156 156 var index = this.selected_index();
157 157 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
158 158 this.select(index+1);
159 159 };
160 160 return this;
161 161 };
162 162
163 163
164 164 Notebook.prototype.select_prev = function () {
165 165 var index = this.selected_index();
166 166 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
167 167 this.select(index-1);
168 168 };
169 169 return this;
170 170 };
171 171
172 172
173 173 Notebook.prototype.selected_index = function () {
174 174 var result = null;
175 175 this.cell_elements().filter(function (index) {
176 176 if ($(this).data("cell").selected === true) {
177 177 result = index;
178 178 };
179 179 });
180 180 return result;
181 181 };
182 182
183 183
184 184 Notebook.prototype.cell_for_msg = function (msg_id) {
185 185 var cell_id = this.msg_cell_map[msg_id];
186 186 var result = null;
187 187 this.cell_elements().filter(function (index) {
188 188 cell = $(this).data("cell");
189 189 if (cell.cell_id === cell_id) {
190 190 result = cell;
191 191 };
192 192 });
193 193 return result;
194 194 };
195 195
196 196
197 197 Notebook.prototype.selected_cell = function () {
198 198 return this.cell_elements().eq(this.selected_index()).data("cell");
199 199 }
200 200
201 201
202 202 // Cell insertion, deletion and moving.
203 203
204 204
205 205 Notebook.prototype.delete_cell = function (index) {
206 206 var i = index || this.selected_index();
207 207 if (i !== null && i >= 0 && i < this.ncells()) {
208 208 this.cell_elements().eq(i).remove();
209 209 if (i === (this.ncells())) {
210 210 this.select(i-1);
211 211 } else {
212 212 this.select(i);
213 213 };
214 214 };
215 215 return this;
216 216 };
217 217
218 218
219 219 Notebook.prototype.append_cell = function (cell) {
220 220 this.element.find('div.end_space').before(cell.element);
221 221 return this;
222 222 };
223 223
224 224
225 225 Notebook.prototype.insert_cell_after = function (cell, index) {
226 226 var ncells = this.ncells();
227 227 if (ncells === 0) {
228 228 this.append_cell(cell);
229 229 return this;
230 230 };
231 231 if (index >= 0 && index < ncells) {
232 232 this.cell_elements().eq(index).after(cell.element);
233 233 };
234 234 return this
235 235 };
236 236
237 237
238 238 Notebook.prototype.insert_cell_before = function (cell, index) {
239 239 var ncells = this.ncells();
240 240 if (ncells === 0) {
241 241 this.append_cell(cell);
242 242 return this;
243 243 };
244 244 if (index >= 0 && index < ncells) {
245 245 this.cell_elements().eq(index).before(cell.element);
246 246 };
247 247 return this;
248 248 };
249 249
250 250
251 251 Notebook.prototype.move_cell_up = function (index) {
252 252 var i = index || this.selected_index();
253 253 if (i !== null && i < this.ncells() && i > 0) {
254 254 var pivot = this.cell_elements().eq(i-1);
255 255 var tomove = this.cell_elements().eq(i);
256 256 if (pivot !== null && tomove !== null) {
257 257 tomove.detach();
258 258 pivot.before(tomove);
259 259 this.select(i-1);
260 260 };
261 261 };
262 262 return this;
263 263 }
264 264
265 265
266 266 Notebook.prototype.move_cell_down = function (index) {
267 267 var i = index || this.selected_index();
268 268 if (i !== null && i < (this.ncells()-1) && i >= 0) {
269 269 var pivot = this.cell_elements().eq(i+1)
270 270 var tomove = this.cell_elements().eq(i)
271 271 if (pivot !== null && tomove !== null) {
272 272 tomove.detach();
273 273 pivot.after(tomove);
274 274 this.select(i+1);
275 275 };
276 276 };
277 277 return this;
278 278 }
279 279
280 280
281 281 Notebook.prototype.sort_cells = function () {
282 282 var ncells = this.ncells();
283 283 var sindex = this.selected_index();
284 284 var swapped;
285 285 do {
286 286 swapped = false
287 287 for (var i=1; i<ncells; i++) {
288 288 current = this.cell_elements().eq(i).data("cell");
289 289 previous = this.cell_elements().eq(i-1).data("cell");
290 290 if (previous.input_prompt_number > current.input_prompt_number) {
291 291 this.move_cell_up(i);
292 292 swapped = true;
293 293 };
294 294 };
295 295 } while (swapped);
296 296 this.select(sindex);
297 297 return this;
298 298 };
299 299
300 300
301 301 Notebook.prototype.insert_code_cell_before = function (index) {
302 302 // TODO: Bounds check for i
303 303 var i = this.index_or_selected(index);
304 304 var cell = new IPython.CodeCell(this);
305 305 cell.set_input_prompt();
306 306 this.insert_cell_before(cell, i);
307 307 this.select(this.find_cell_index(cell));
308 308 return cell;
309 309 }
310 310
311 311
312 312 Notebook.prototype.insert_code_cell_after = function (index) {
313 313 // TODO: Bounds check for i
314 314 var i = this.index_or_selected(index);
315 315 var cell = new IPython.CodeCell(this);
316 316 cell.set_input_prompt();
317 317 this.insert_cell_after(cell, i);
318 318 this.select(this.find_cell_index(cell));
319 319 return cell;
320 320 }
321 321
322 322
323 323 Notebook.prototype.insert_text_cell_before = function (index) {
324 324 // TODO: Bounds check for i
325 325 var i = this.index_or_selected(index);
326 326 var cell = new IPython.TextCell(this);
327 327 cell.config_mathjax();
328 328 this.insert_cell_before(cell, i);
329 329 this.select(this.find_cell_index(cell));
330 330 return cell;
331 331 }
332 332
333 333
334 334 Notebook.prototype.insert_text_cell_after = function (index) {
335 335 // TODO: Bounds check for i
336 336 var i = this.index_or_selected(index);
337 337 var cell = new IPython.TextCell(this);
338 338 cell.config_mathjax();
339 339 this.insert_cell_after(cell, i);
340 340 this.select(this.find_cell_index(cell));
341 341 return cell;
342 342 }
343 343
344 344
345 345 Notebook.prototype.text_to_code = function (index) {
346 346 // TODO: Bounds check for i
347 347 var i = this.index_or_selected(index);
348 348 var source_element = this.cell_elements().eq(i);
349 349 var source_cell = source_element.data("cell");
350 350 if (source_cell instanceof IPython.TextCell) {
351 351 this.insert_code_cell_after(i);
352 352 var target_cell = this.cells()[i+1];
353 353 target_cell.set_code(source_cell.get_text());
354 354 source_element.remove();
355 355 };
356 356 };
357 357
358 358
359 359 Notebook.prototype.code_to_text = function (index) {
360 360 // TODO: Bounds check for i
361 361 var i = this.index_or_selected(index);
362 362 var source_element = this.cell_elements().eq(i);
363 363 var source_cell = source_element.data("cell");
364 364 if (source_cell instanceof IPython.CodeCell) {
365 365 this.insert_text_cell_after(i);
366 366 var target_cell = this.cells()[i+1];
367 367 var text = source_cell.get_code();
368 368 if (text === "") {text = target_cell.placeholder;};
369 369 target_cell.set_text(text);
370 370 source_element.remove();
371 371 target_cell.edit();
372 372 };
373 373 };
374 374
375 375
376 376 // Cell collapsing
377 377
378 378 Notebook.prototype.collapse = function (index) {
379 379 var i = this.index_or_selected(index);
380 380 this.cells()[i].collapse();
381 381 };
382 382
383 383
384 384 Notebook.prototype.expand = function (index) {
385 385 var i = this.index_or_selected(index);
386 386 this.cells()[i].expand();
387 387 };
388 388
389 389
390 390 // Kernel related things
391 391
392 392 Notebook.prototype.start_kernel = function () {
393 393 this.kernel = new IPython.Kernel();
394 this.kernel.start_kernel($.proxy(this.kernel_started, this));
394 var notebook_id = IPython.save_widget.get_notebook_id();
395 this.kernel.start_kernel(notebook_id, $.proxy(this.kernel_started, this));
395 396 };
396 397
397 398
398 399 Notebook.prototype.handle_shell_reply = function (e) {
399 400 reply = $.parseJSON(e.data);
400 401 var header = reply.header;
401 402 var content = reply.content;
402 403 var msg_type = header.msg_type;
403 404 // console.log(reply);
404 405 var cell = this.cell_for_msg(reply.parent_header.msg_id);
405 406 if (msg_type === "execute_reply") {
406 407 cell.set_input_prompt(content.execution_count);
407 408 } else if (msg_type === "complete_reply") {
408 409 cell.finish_completing(content.matched_text, content.matches);
409 410 };
410 411 var payload = content.payload || [];
411 412 this.handle_payload(payload);
412 413 };
413 414
414 415
415 416 Notebook.prototype.handle_payload = function (payload) {
416 417 var l = payload.length;
417 418 if (l > 0) {
418 419 IPython.pager.clear();
419 420 IPython.pager.expand();
420 421 };
421 422 for (var i=0; i<l; i++) {
422 423 IPython.pager.append_text(payload[i].text);
423 424 };
424 425 };
425 426
426 427
427 428 Notebook.prototype.handle_iopub_reply = function (e) {
428 429 reply = $.parseJSON(e.data);
429 430 var content = reply.content;
430 431 // console.log(reply);
431 432 var msg_type = reply.header.msg_type;
432 433 var cell = this.cell_for_msg(reply.parent_header.msg_id);
433 434 if (msg_type === "stream") {
434 435 cell.expand();
435 436 cell.append_stream(content.data + "\n");
436 437 } else if (msg_type === "display_data") {
437 438 cell.expand();
438 439 cell.append_display_data(content.data);
439 440 } else if (msg_type === "pyout") {
440 441 cell.expand();
441 442 cell.append_pyout(content.data, content.execution_count)
442 443 } else if (msg_type === "pyerr") {
443 444 cell.expand();
444 445 cell.append_pyerr(content.ename, content.evalue, content.traceback);
445 446 } else if (msg_type === "status") {
446 447 if (content.execution_state === "busy") {
447 448 IPython.kernel_status_widget.status_busy();
448 449 } else if (content.execution_state === "idle") {
449 450 IPython.kernel_status_widget.status_idle();
450 451 };
451 452 }
452 453 };
453 454
454 455
455 456 Notebook.prototype.kernel_started = function () {
456 457 console.log("Kernel started: ", this.kernel.kernel_id);
457 458 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
458 459 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
459 460 };
460 461
461 462
462 463 Notebook.prototype.execute_selected_cell = function (options) {
463 464 // add_new: should a new cell be added if we are at the end of the nb
464 465 // terminal: execute in terminal mode, which stays in the current cell
465 466 default_options = {terminal: false, add_new: true}
466 467 $.extend(default_options, options)
467 468 var that = this;
468 469 var cell = that.selected_cell();
469 470 var cell_index = that.find_cell_index(cell);
470 471 if (cell instanceof IPython.CodeCell) {
471 472 cell.clear_output();
472 473 var code = cell.get_code();
473 474 var msg_id = that.kernel.execute(cell.get_code());
474 475 that.msg_cell_map[msg_id] = cell.cell_id;
475 476 } else if (cell instanceof IPython.TextCell) {
476 477 cell.render();
477 478 }
478 479 if (default_options.terminal) {
479 480 cell.clear_input();
480 481 } else {
481 482 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
482 483 that.insert_code_cell_after();
483 484 // If we are adding a new cell at the end, scroll down to show it.
484 485 that.scroll_to_bottom();
485 486 } else {
486 487 that.select(cell_index+1);
487 488 };
488 489 };
489 490 };
490 491
491 492
492 493 Notebook.prototype.execute_all_cells = function () {
493 494 var ncells = this.ncells();
494 495 for (var i=0; i<ncells; i++) {
495 496 this.select(i);
496 497 this.execute_selected_cell({add_new:false});
497 498 };
498 499 this.scroll_to_bottom();
499 500 };
500 501
501 502
502 503 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
503 504 var msg_id = this.kernel.complete(line, cursor_pos);
504 505 this.msg_cell_map[msg_id] = cell.cell_id;
505 506 };
506 507
507 508 // Persistance and loading
508 509
509 510
510 511 Notebook.prototype.fromJSON = function (data) {
511 512 var ncells = this.ncells();
512 513 for (var i=0; i<ncells; i++) {
513 514 // Always delete cell 0 as they get renumbered as they are deleted.
514 515 this.delete_cell(0);
515 516 };
516 517 // Only handle 1 worksheet for now.
517 518 var worksheet = data.worksheets[0];
518 519 if (worksheet !== undefined) {
519 520 var new_cells = worksheet.cells;
520 521 ncells = new_cells.length;
521 522 var cell_data = null;
522 523 var new_cell = null;
523 524 for (var i=0; i<ncells; i++) {
524 525 cell_data = new_cells[i];
525 526 if (cell_data.cell_type == 'code') {
526 527 new_cell = this.insert_code_cell_after();
527 528 new_cell.fromJSON(cell_data);
528 529 } else if (cell_data.cell_type === 'text') {
529 530 new_cell = this.insert_text_cell_after();
530 531 new_cell.fromJSON(cell_data);
531 532 };
532 533 };
533 534 };
534 535 };
535 536
536 537
537 538 Notebook.prototype.toJSON = function () {
538 539 var cells = this.cells();
539 540 var ncells = cells.length;
540 541 cell_array = new Array(ncells);
541 542 for (var i=0; i<ncells; i++) {
542 543 cell_array[i] = cells[i].toJSON();
543 544 };
544 545 data = {
545 546 // Only handle 1 worksheet for now.
546 547 worksheets : [{cells:cell_array}]
547 548 }
548 549 return data
549 550 };
550 551
551 552 Notebook.prototype.save_notebook = function () {
552 553 if (IPython.save_widget.test_notebook_name()) {
553 554 var notebook_id = IPython.save_widget.get_notebook_id();
554 555 var nbname = IPython.save_widget.get_notebook_name();
555 556 // We may want to move the name/id/nbformat logic inside toJSON?
556 557 var data = this.toJSON();
557 558 data.name = nbname;
558 559 data.nbformat = 2;
559 560 data.id = notebook_id
560 561 // We do the call with settings so we can set cache to false.
561 562 var settings = {
562 563 processData : false,
563 564 cache : false,
564 565 type : "PUT",
565 566 data : JSON.stringify(data),
566 567 headers : {'Content-Type': 'application/json'},
567 568 success : $.proxy(this.notebook_saved,this)
568 569 };
569 570 IPython.save_widget.status_saving();
570 571 $.ajax("/notebooks/" + notebook_id, settings);
571 572 };
572 573 };
573 574
574 575
575 576 Notebook.prototype.notebook_saved = function (data, status, xhr) {
576 577 IPython.save_widget.status_save();
577 578 }
578 579
579 580
580 581 Notebook.prototype.load_notebook = function (callback) {
581 582 var that = this;
582 583 var notebook_id = IPython.save_widget.get_notebook_id();
583 584 // We do the call with settings so we can set cache to false.
584 585 var settings = {
585 586 processData : false,
586 587 cache : false,
587 588 type : "GET",
588 589 dataType : "json",
589 590 success : function (data, status, xhr) {
590 591 that.notebook_loaded(data, status, xhr);
591 592 if (callback !== undefined) {
592 593 callback();
593 594 };
594 595 }
595 596 };
596 597 IPython.save_widget.status_loading();
597 598 $.ajax("/notebooks/" + notebook_id, settings);
598 599 }
599 600
600 601
601 602 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
602 603 this.fromJSON(data);
603 604 if (this.ncells() === 0) {
604 605 this.insert_code_cell_after();
605 606 };
606 607 IPython.save_widget.status_save();
607 608 IPython.save_widget.set_notebook_name(data.name);
608 609 this.start_kernel();
609 610 // fromJSON always selects the last cell inserted. We need to wait
610 611 // until that is done before scrolling to the top.
611 612 setTimeout(function () {
612 613 IPython.notebook.select(0);
613 614 IPython.notebook.scroll_to_top();
614 615 }, 50);
615 616 };
616 617
617 618 IPython.Notebook = Notebook;
618 619
619 620 return IPython;
620 621
621 622 }(IPython));
622 623
General Comments 0
You need to be logged in to leave comments. Login now