##// END OF EJS Templates
Massive work on the notebook document format....
Brian E. Granger -
Show More
@@ -0,0 +1,195
1 #-----------------------------------------------------------------------------
2 # Copyright (C) 2011 The IPython Development Team
3 #
4 # Distributed under the terms of the BSD License. The full license is in
5 # the file COPYING.txt, distributed as part of this software.
6 #-----------------------------------------------------------------------------
7
8 #-----------------------------------------------------------------------------
9 # Imports
10 #-----------------------------------------------------------------------------
11
12 import datetime
13 import os
14 import uuid
15
16 from tornado import web
17
18 from IPython.config.configurable import Configurable
19 from IPython.nbformat import current
20 from IPython.utils.traitlets import Unicode, List, Dict
21
22
23 #-----------------------------------------------------------------------------
24 # Code
25 #-----------------------------------------------------------------------------
26
27
28 class NotebookManager(Configurable):
29
30 notebook_dir = Unicode(os.getcwd())
31 filename_ext = Unicode(u'.ipynb')
32 allowed_formats = List([u'json',u'xml',u'py'])
33
34 # Map notebook_ids to notebook names
35 mapping = Dict()
36 # Map notebook names to notebook_ids
37 rev_mapping = Dict()
38
39 def list_notebooks(self):
40 """List all notebooks in the notebook dir.
41
42 This returns a list of dicts of the form::
43
44 dict(notebook_id=notebook,name=name)
45 """
46 names = os.listdir(self.notebook_dir)
47 names = [name.strip(self.filename_ext)\
48 for name in names if name.endswith(self.filename_ext)]
49 data = []
50 for name in names:
51 if name not in self.rev_mapping:
52 notebook_id = self.new_notebook_id(name)
53 else:
54 notebook_id = self.rev_mapping[name]
55 data.append(dict(notebook_id=notebook_id,name=name))
56 return data
57
58 def new_notebook_id(self, name):
59 """Generate a new notebook_id for a name and store its mappings."""
60 notebook_id = unicode(uuid.uuid4())
61 self.mapping[notebook_id] = name
62 self.rev_mapping[name] = notebook_id
63 return notebook_id
64
65 def delete_notebook_id(self, notebook_id):
66 """Delete a notebook's id only. This doesn't delete the actual notebook."""
67 name = self.mapping[notebook_id]
68 del self.mapping[notebook_id]
69 del self.rev_mapping[name]
70
71 def notebook_exists(self, notebook_id):
72 """Does a notebook exist?"""
73 if notebook_id not in self.mapping:
74 return False
75 path = self.get_path_by_name(self.mapping[notebook_id])
76 if not os.path.isfile(path):
77 return False
78 return True
79
80 def find_path(self, notebook_id):
81 """Return a full path to a notebook given its notebook_id."""
82 try:
83 name = self.mapping[notebook_id]
84 except KeyError:
85 raise web.HTTPError(404)
86 return self.get_path_by_name(name)
87
88 def get_path_by_name(self, name):
89 """Return a full path to a notebook given its name."""
90 filename = name + self.filename_ext
91 path = os.path.join(self.notebook_dir, filename)
92 return path
93
94 def get_notebook(self, notebook_id, format=u'json'):
95 """Get the representation of a notebook in format by notebook_id."""
96 format = unicode(format)
97 if format not in self.allowed_formats:
98 raise web.HTTPError(415)
99 last_modified, nb = self.get_notebook_object(notebook_id)
100 data = current.writes(nb, format)
101 name = nb.get('name','notebook')
102 return last_modified, name, data
103
104 def get_notebook_object(self, notebook_id):
105 """Get the NotebookNode representation of a notebook by notebook_id."""
106 path = self.find_path(notebook_id)
107 if not os.path.isfile(path):
108 raise web.HTTPError(404)
109 info = os.stat(path)
110 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
111 try:
112 with open(path,'r') as f:
113 s = f.read()
114 try:
115 # v2 and later have xml in the .ipynb files
116 nb = current.reads(s, 'xml')
117 except:
118 # v1 had json in the .ipynb files
119 nb = current.reads(s, 'json')
120 except:
121 raise web.HTTPError(404)
122 return last_modified, nb
123
124 def save_new_notebook(self, data, format=u'json'):
125 """Save a new notebook and return its notebook_id."""
126 if format not in self.allowed_formats:
127 raise web.HTTPError(415)
128 try:
129 nb = current.reads(data, format)
130 except:
131 raise web.HTTPError(400)
132 try:
133 name = nb.name
134 except AttributeError:
135 raise web.HTTPError(400)
136 notebook_id = self.new_notebook_id(name)
137 self.save_notebook_object(notebook_id, nb)
138 return notebook_id
139
140 def save_notebook(self, notebook_id, data, format=u'json'):
141 """Save an existing notebook by notebook_id."""
142 if format not in self.allowed_formats:
143 raise web.HTTPError(415)
144 try:
145 nb = current.reads(data, format)
146 except:
147 raise web.HTTPError(400)
148 self.save_notebook_object(notebook_id, nb)
149
150 def save_notebook_object(self, notebook_id, nb):
151 """Save an existing notebook object by notebook_id."""
152 if notebook_id not in self.mapping:
153 raise web.HTTPError(404)
154 old_name = self.mapping[notebook_id]
155 try:
156 new_name = nb.name
157 except AttributeError:
158 raise web.HTTPError(400)
159 path = self.get_path_by_name(new_name)
160 try:
161 with open(path,'w') as f:
162 current.write(nb, f, u'xml')
163 except:
164 raise web.HTTPError(400)
165 if old_name != new_name:
166 old_path = self.get_path_by_name(old_name)
167 if os.path.isfile(old_path):
168 os.unlink(old_path)
169 self.mapping[notebook_id] = new_name
170 self.rev_mapping[new_name] = notebook_id
171
172 def delete_notebook(self, notebook_id):
173 """Delete notebook by notebook_id."""
174 path = self.find_path(notebook_id)
175 if not os.path.isfile(path):
176 raise web.HTTPError(404)
177 os.unlink(path)
178 self.delete_notebook_id(notebook_id)
179
180 def new_notebook(self):
181 """Create a new notebook and returns its notebook_id."""
182 i = 0
183 while True:
184 name = u'Untitled%i' % i
185 path = self.get_path_by_name(name)
186 if not os.path.isfile(path):
187 break
188 else:
189 i = i+1
190 notebook_id = self.new_notebook_id(name)
191 nb = current.new_notebook(name=name, id=notebook_id)
192 with open(path,'w') as f:
193 current.write(nb, f, u'xml')
194 return notebook_id
195
@@ -1,116 +1,125
1 1 """Tornado handlers for the notebook."""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Imports
5 5 #-----------------------------------------------------------------------------
6 6
7 import datetime
8 7 import json
9 8 import logging
10 import os
11 9 import urllib
12 10
13 11 from tornado import web
14 12 from tornado import websocket
15 13
14
16 15 #-----------------------------------------------------------------------------
17 16 # Handlers
18 17 #-----------------------------------------------------------------------------
19 18
20 19
21 20 class MainHandler(web.RequestHandler):
22 21 def get(self):
23 self.render('notebook.html')
22 notebook_id = self.application.notebook_manager.new_notebook()
23 self.render('notebook.html', notebook_id=notebook_id)
24
25
26 class NamedNotebookHandler(web.RequestHandler):
27 def get(self, notebook_id):
28 nbm = self.application.notebook_manager
29 if not nbm.notebook_exists(notebook_id):
30 raise web.HTTPError(404)
31 self.render('notebook.html', notebook_id=notebook_id)
24 32
25 33
26 34 class KernelHandler(web.RequestHandler):
27 35
28 36 def get(self):
29 37 self.write(json.dumps(self.application.kernel_ids))
30 38
31 39 def post(self):
32 40 kernel_id = self.application.start_kernel()
41 self.set_header('Location', '/'+kernel_id)
33 42 self.write(json.dumps(kernel_id))
34 43
35 44
36 45 class KernelActionHandler(web.RequestHandler):
37 46
38 47 def post(self, kernel_id, action):
39 48 # TODO: figure out a better way of handling RPC style calls.
40 49 if action == 'interrupt':
41 50 self.application.interrupt_kernel(kernel_id)
42 51 if action == 'restart':
43 52 new_kernel_id = self.application.restart_kernel(kernel_id)
44 53 self.write(json.dumps(new_kernel_id))
45 54
46 55
47 56 class ZMQStreamHandler(websocket.WebSocketHandler):
48 57
49 58 def initialize(self, stream_name):
50 59 self.stream_name = stream_name
51 60
52 61 def open(self, kernel_id):
53 62 self.router = self.application.get_router(kernel_id, self.stream_name)
54 63 self.client_id = self.router.register_client(self)
55 64 logging.info("Connection open: %s, %s" % (kernel_id, self.client_id))
56 65
57 66 def on_message(self, msg):
58 67 self.router.forward_msg(self.client_id, msg)
59 68
60 69 def on_close(self):
61 70 self.router.unregister_client(self.client_id)
62 71 logging.info("Connection closed: %s" % self.client_id)
63 72
64 73
65 74 class NotebookRootHandler(web.RequestHandler):
66 75
67 76 def get(self):
68 files = os.listdir(os.getcwd())
69 files = [file for file in files if file.endswith(".ipynb")]
77 nbm = self.application.notebook_manager
78 files = nbm.list_notebooks()
70 79 self.write(json.dumps(files))
71 80
81 def post(self):
82 nbm = self.application.notebook_manager
83 body = self.request.body.strip()
84 format = self.get_argument('format', default='json')
85 if body:
86 notebook_id = nbm.save_new_notebook(body, format)
87 else:
88 notebook_id = nbm.new_notebook()
89 self.set_header('Location', '/'+notebook_id)
90 self.write(json.dumps(notebook_id))
72 91
73 class NotebookHandler(web.RequestHandler):
74
75 SUPPORTED_METHODS = ("GET", "DELETE", "PUT")
76 92
77 def find_path(self, filename):
78 filename = urllib.unquote(filename)
79 if not filename.endswith('.ipynb'):
80 raise web.HTTPError(400)
81 path = os.path.join(os.getcwd(), filename)
82 return path
93 class NotebookHandler(web.RequestHandler):
83 94
84 def get(self, filename):
85 path = self.find_path(filename)
86 if not os.path.isfile(path):
87 raise web.HTTPError(404)
88 info = os.stat(path)
89 self.set_header("Content-Type", "application/unknown")
90 self.set_header("Last-Modified", datetime.datetime.utcfromtimestamp(
91 info.st_mtime))
92 f = open(path, "r")
93 try:
94 self.finish(f.read())
95 finally:
96 f.close()
97
98 def put(self, filename):
99 path = self.find_path(filename)
100 f = open(path, "w")
101 f.write(self.request.body)
102 f.close()
95 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
96
97 def get(self, notebook_id):
98 nbm = self.application.notebook_manager
99 format = self.get_argument('format', default='json')
100 last_mod, name, data = nbm.get_notebook(notebook_id, format)
101 if format == u'json':
102 self.set_header('Content-Type', 'application/json')
103 self.set_header('Content-Disposition','attachment; filename=%s.json' % name)
104 elif format == u'xml':
105 self.set_header('Content-Type', 'text/xml')
106 self.set_header('Content-Disposition','attachment; filename=%s.ipynb' % name)
107 elif format == u'py':
108 self.set_header('Content-Type', 'text/plain')
109 self.set_header('Content-Disposition','attachment; filename=%s.py' % name)
110 self.set_header('Last-Modified', last_mod)
111 self.finish(data)
112
113 def put(self, notebook_id):
114 nbm = self.application.notebook_manager
115 format = self.get_argument('format', default='json')
116 nbm.save_notebook(notebook_id, self.request.body, format)
117 self.set_status(204)
103 118 self.finish()
104 119
105 def delete(self, filename):
106 path = self.find_path(filename)
107 if not os.path.isfile(path):
108 raise web.HTTPError(404)
109 os.unlink(path)
120 def delete(self, notebook_id):
121 nbm = self.application.notebook_manager
122 nbm.delete_notebook(notebook_id)
110 123 self.set_status(204)
111 124 self.finish()
112 125
113
114
115
116
@@ -1,264 +1,269
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
31 from sessionmanager import SessionManager
32 from handlers import (
33 MainHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler,
30 from .kernelmanager import KernelManager
31 from .sessionmanager import SessionManager
32 from .handlers import (
33 MainHandler, NamedNotebookHandler,
34 KernelHandler, KernelActionHandler, ZMQStreamHandler,
34 35 NotebookRootHandler, NotebookHandler
35 36 )
36 from routers import IOPubStreamRouter, ShellStreamRouter
37 from .routers import IOPubStreamRouter, ShellStreamRouter
38 from .notebookmanager import NotebookManager
37 39
38 40 from IPython.core.application import BaseIPythonApplication
39 41 from IPython.core.profiledir import ProfileDir
40 42 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
41 43 from IPython.zmq.session import Session
42 44 from IPython.zmq.zmqshell import ZMQInteractiveShell
43 45 from IPython.zmq.ipkernel import (
44 46 flags as ipkernel_flags,
45 47 aliases as ipkernel_aliases,
46 48 IPKernelApp
47 49 )
48 50 from IPython.utils.traitlets import Dict, Unicode, Int, Any, List, Enum
49 51
50 52 #-----------------------------------------------------------------------------
51 53 # Module globals
52 54 #-----------------------------------------------------------------------------
53 55
54 56 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
55 57 _kernel_action_regex = r"(?P<action>restart|interrupt)"
58 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
56 59
57 60 LOCALHOST = '127.0.0.1'
58 61
59 62 #-----------------------------------------------------------------------------
60 63 # The Tornado web application
61 64 #-----------------------------------------------------------------------------
62 65
63 66 class NotebookWebApplication(web.Application):
64 67
65 68 def __init__(self, kernel_manager, log, kernel_argv, config):
66 69 handlers = [
67 70 (r"/", MainHandler),
71 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
68 72 (r"/kernels", KernelHandler),
69 73 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
70 74 (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')),
71 75 (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')),
72 76 (r"/notebooks", NotebookRootHandler),
73 (r"/notebooks/([^/]+)", NotebookHandler)
77 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler)
74 78 ]
75 79 settings = dict(
76 80 template_path=os.path.join(os.path.dirname(__file__), "templates"),
77 81 static_path=os.path.join(os.path.dirname(__file__), "static"),
78 82 )
79 83 web.Application.__init__(self, handlers, **settings)
80 84
81 85 self.kernel_manager = kernel_manager
82 86 self.log = log
83 87 self.kernel_argv = kernel_argv
84 88 self.config = config
85 89 self._routers = {}
86 90 self._session_dict = {}
91 self.notebook_manager = NotebookManager(config=self.config)
87 92
88 93 #-------------------------------------------------------------------------
89 94 # Methods for managing kernels and sessions
90 95 #-------------------------------------------------------------------------
91 96
92 97 @property
93 98 def kernel_ids(self):
94 99 return self.kernel_manager.kernel_ids
95 100
96 101 def start_kernel(self):
97 102 kwargs = dict()
98 103 kwargs['extra_arguments'] = self.kernel_argv
99 104 kernel_id = self.kernel_manager.start_kernel(**kwargs)
100 105 self.log.info("Kernel started: %s" % kernel_id)
101 106 self.log.debug("Kernel args: %r" % kwargs)
102 107 self.start_session_manager(kernel_id)
103 108 return kernel_id
104 109
105 110 def start_session_manager(self, kernel_id):
106 111 sm = self.kernel_manager.create_session_manager(kernel_id)
107 112 self._session_dict[kernel_id] = sm
108 113 iopub_stream = sm.get_iopub_stream()
109 114 shell_stream = sm.get_shell_stream()
110 115 iopub_router = IOPubStreamRouter(
111 116 zmq_stream=iopub_stream, session=sm.session, config=self.config
112 117 )
113 118 shell_router = ShellStreamRouter(
114 119 zmq_stream=shell_stream, session=sm.session, config=self.config
115 120 )
116 121 self._routers[(kernel_id, 'iopub')] = iopub_router
117 122 self._routers[(kernel_id, 'shell')] = shell_router
118 123
119 124 def kill_kernel(self, kernel_id):
120 125 sm = self._session_dict.pop(kernel_id)
121 126 sm.stop()
122 127 self.kernel_manager.kill_kernel(kernel_id)
123 128 self.log.info("Kernel killed: %s" % kernel_id)
124 129
125 130 def interrupt_kernel(self, kernel_id):
126 131 self.kernel_manager.interrupt_kernel(kernel_id)
127 132 self.log.debug("Kernel interrupted: %s" % kernel_id)
128 133
129 134 def restart_kernel(self, kernel_id):
130 135 # Create the new kernel first so we can move the clients over.
131 136 new_kernel_id = self.start_kernel()
132 137
133 138 # Copy the clients over to the new routers.
134 139 old_iopub_router = self.get_router(kernel_id, 'iopub')
135 140 old_shell_router = self.get_router(kernel_id, 'shell')
136 141 new_iopub_router = self.get_router(new_kernel_id, 'iopub')
137 142 new_shell_router = self.get_router(new_kernel_id, 'shell')
138 143 new_iopub_router.copy_clients(old_iopub_router)
139 144 new_shell_router.copy_clients(old_shell_router)
140 145
141 146 # Now shutdown the old session and the kernel.
142 147 # TODO: This causes a hard crash in ZMQStream.close, which sets
143 148 # self.socket to None to hastily. We will need to fix this in PyZMQ
144 149 # itself. For now, we just leave the old kernel running :(
145 150 # Maybe this is fixed now, but nothing was changed really.
146 151 self.kill_kernel(kernel_id)
147 152
148 153 self.log.debug("Kernel restarted: %s -> %s" % (kernel_id, new_kernel_id))
149 154 return new_kernel_id
150 155
151 156 def get_router(self, kernel_id, stream_name):
152 157 router = self._routers[(kernel_id, stream_name)]
153 158 return router
154 159
155 160
156 161
157 162 #-----------------------------------------------------------------------------
158 163 # Aliases and Flags
159 164 #-----------------------------------------------------------------------------
160 165
161 166 flags = dict(ipkernel_flags)
162 167
163 168 # the flags that are specific to the frontend
164 169 # these must be scrubbed before being passed to the kernel,
165 170 # or it will raise an error on unrecognized flags
166 171 notebook_flags = []
167 172
168 173 aliases = dict(ipkernel_aliases)
169 174
170 175 aliases.update(dict(
171 176 ip = 'IPythonNotebookApp.ip',
172 177 port = 'IPythonNotebookApp.port',
173 178 colors = 'ZMQInteractiveShell.colors',
174 179 editor = 'RichIPythonWidget.editor',
175 180 ))
176 181
177 182 #-----------------------------------------------------------------------------
178 183 # IPythonNotebookApp
179 184 #-----------------------------------------------------------------------------
180 185
181 186 class IPythonNotebookApp(BaseIPythonApplication):
182 187 name = 'ipython-notebook'
183 188 default_config_file_name='ipython_notebook_config.py'
184 189
185 190 description = """
186 191 The IPython HTML Notebook.
187 192
188 193 This launches a Tornado based HTML Notebook Server that serves up an
189 194 HTML5/Javascript Notebook client.
190 195 """
191 196
192 197 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
193 198 KernelManager, SessionManager, RichIPythonWidget]
194 199 flags = Dict(flags)
195 200 aliases = Dict(aliases)
196 201
197 202 kernel_argv = List(Unicode)
198 203
199 204 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
200 205 default_value=logging.INFO,
201 206 config=True,
202 207 help="Set the log level by value or name.")
203 208
204 209 # connection info:
205 210 ip = Unicode(LOCALHOST, config=True,
206 211 help="The IP address the notebook server will listen on."
207 212 )
208 213
209 214 port = Int(8888, config=True,
210 215 help="The port the notebook server will listen on."
211 216 )
212 217
213 218 # the factory for creating a widget
214 219 widget_factory = Any(RichIPythonWidget)
215 220
216 221 def parse_command_line(self, argv=None):
217 222 super(IPythonNotebookApp, self).parse_command_line(argv)
218 223 if argv is None:
219 224 argv = sys.argv[1:]
220 225
221 226 self.kernel_argv = list(argv) # copy
222 227 # kernel should inherit default config file from frontend
223 228 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
224 229 # scrub frontend-specific flags
225 230 for a in argv:
226 231 if a.startswith('-') and a.lstrip('-') in notebook_flags:
227 232 self.kernel_argv.remove(a)
228 233
229 234 def init_kernel_manager(self):
230 235 # Don't let Qt or ZMQ swallow KeyboardInterupts.
231 236 signal.signal(signal.SIGINT, signal.SIG_DFL)
232 237
233 238 # Create a KernelManager and start a kernel.
234 239 self.kernel_manager = KernelManager(config=self.config, log=self.log)
235 240
236 241 def init_logging(self):
237 242 super(IPythonNotebookApp, self).init_logging()
238 243 # This prevents double log messages because tornado use a root logger that
239 244 # self.log is a child of. The logging module dipatches log messages to a log
240 245 # and all of its ancenstors until propagate is set to False.
241 246 self.log.propagate = False
242 247
243 248 def initialize(self, argv=None):
244 249 super(IPythonNotebookApp, self).initialize(argv)
245 250 self.init_kernel_manager()
246 251 self.web_app = NotebookWebApplication(
247 252 self.kernel_manager, self.log, self.kernel_argv, self.config
248 253 )
249 254 self.http_server = httpserver.HTTPServer(self.web_app)
250 255 self.http_server.listen(self.port)
251 256
252 257 def start(self):
253 258 self.log.info("The IPython Notebook is running at: http://%s:%i" % (self.ip, self.port))
254 259 ioloop.IOLoop.instance().start()
255 260
256 261 #-----------------------------------------------------------------------------
257 262 # Main entry point
258 263 #-----------------------------------------------------------------------------
259 264
260 265 def launch_new_instance():
261 266 app = IPythonNotebookApp()
262 267 app.initialize()
263 268 app.start()
264 269
@@ -1,118 +1,118
1 1 """Routers that connect WebSockets to ZMQ sockets."""
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 uuid
15 15 from Queue import Queue
16 16 import json
17 17
18 18 from IPython.config.configurable import Configurable
19 19 from IPython.utils.traitlets import Instance, Int, Dict
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Classes
23 23 #-----------------------------------------------------------------------------
24 24
25 25 class ZMQStreamRouter(Configurable):
26 26
27 27 zmq_stream = Instance('zmq.eventloop.zmqstream.ZMQStream')
28 28 session = Instance('IPython.zmq.session.Session')
29 29 max_msg_size = Int(2048, config=True, help="""
30 30 The max raw message size accepted from the browser
31 31 over a WebSocket connection.
32 32 """)
33 33
34 34 _clients = Dict()
35 35
36 36 def __init__(self, **kwargs):
37 37 super(ZMQStreamRouter,self).__init__(**kwargs)
38 38 self.zmq_stream.on_recv(self._on_zmq_reply)
39 39
40 40 def register_client(self, client):
41 41 """Register a client, returning a client uuid."""
42 42 client_id = uuid.uuid4()
43 43 self._clients[client_id] = client
44 44 return client_id
45 45
46 46 def unregister_client(self, client_id):
47 47 """Unregister a client by its client uuid."""
48 48 del self._clients[client_id]
49 49
50 50 def copy_clients(self, router):
51 51 """Copy the clients of another router to this one.
52 52
53 53 This is used to enable the backend zeromq stream to disconnect
54 54 and reconnect while the WebSocket connections to browsers
55 55 remain, such as when a kernel is restarted.
56 56 """
57 57 for client_id, client in router._clients.items():
58 58 client.router = self
59 59 self._clients[client_id] = client
60 60
61 61 def forward_msg(self, client_id, msg):
62 62 """Forward a msg to a client by its id.
63 63
64 64 The default implementation of this will fail silently if a message
65 65 arrives on a socket that doesn't support it. This method should
66 66 use max_msg_size to check and silently discard message that are too
67 67 long."""
68 68 pass
69 69
70 70 def _on_zmq_reply(self, msg_list):
71 71 """Handle a message the ZMQ stream sends to the router.
72 72
73 73 Usually, this is where the return message will be written to
74 74 clients that need it using client.write_message().
75 75 """
76 76 pass
77 77
78 78 def _reserialize_reply(self, msg_list):
79 79 """Reserialize a reply message using JSON.
80 80
81 81 This takes the msg list from the ZMQ socket, unserializes it using
82 82 self.session and then serializes the result using JSON. This method
83 83 should be used by self._on_zmq_reply to build messages that can
84 84 be sent back to the browser.
85 85 """
86 86 idents, msg_list = self.session.feed_identities(msg_list)
87 87 msg = self.session.unserialize(msg_list)
88 88 msg['header'].pop('date')
89 89 msg.pop('buffers')
90 90 return json.dumps(msg)
91 91
92 92
93 93 class IOPubStreamRouter(ZMQStreamRouter):
94 94
95 95 def _on_zmq_reply(self, msg_list):
96 96 msg = self._reserialize_reply(msg_list)
97 97 for client_id, client in self._clients.items():
98 98 client.write_message(msg)
99 99
100 100
101 101 class ShellStreamRouter(ZMQStreamRouter):
102 102
103 103 _request_queue = Instance(Queue,(),{})
104 104
105 105 def _on_zmq_reply(self, msg_list):
106 106 msg = self._reserialize_reply(msg_list)
107 107 client_id = self._request_queue.get(block=False)
108 108 client = self._clients.get(client_id)
109 109 if client is not None:
110 110 client.write_message(msg)
111 111
112 112 def forward_msg(self, client_id, msg):
113 113 if len(msg) < self.max_msg_size:
114 114 msg = json.loads(msg)
115 to_send = self.session.serialize(msg)
115 # to_send = self.session.serialize(msg)
116 116 self._request_queue.put(client_id)
117 117 self.session.send(self.zmq_stream, msg)
118 118
@@ -1,378 +1,385
1 1 /**
2 2 * HTML5 ✰ Boilerplate
3 3 *
4 4 * style.css contains a reset, font normalization and some base styles.
5 5 *
6 6 * Credit is left where credit is due.
7 7 * Much inspiration was taken from these projects:
8 8 * - yui.yahooapis.com/2.8.1/build/base/base.css
9 9 * - camendesign.com/design/
10 10 * - praegnanz.de/weblog/htmlcssjs-kickstart
11 11 */
12 12
13 13
14 14 /**
15 15 * html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline)
16 16 * v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark
17 17 * html5doctor.com/html-5-reset-stylesheet/
18 18 */
19 19
20 20 html, body, div, span, object, iframe,
21 21 h1, h2, h3, h4, h5, h6, p, blockquote, pre,
22 22 abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
23 23 small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
24 24 fieldset, form, label, legend,
25 25 table, caption, tbody, tfoot, thead, tr, th, td,
26 26 article, aside, canvas, details, figcaption, figure,
27 27 footer, header, hgroup, menu, nav, section, summary,
28 28 time, mark, audio, video {
29 29 margin: 0;
30 30 padding: 0;
31 31 border: 0;
32 32 font-size: 100%;
33 33 font: inherit;
34 34 vertical-align: baseline;
35 35 }
36 36
37 37 article, aside, details, figcaption, figure,
38 38 footer, header, hgroup, menu, nav, section {
39 39 display: block;
40 40 }
41 41
42 42 blockquote, q { quotes: none; }
43 43
44 44 blockquote:before, blockquote:after,
45 45 q:before, q:after { content: ""; content: none; }
46 46
47 47 ins { background-color: #ff9; color: #000; text-decoration: none; }
48 48
49 49 mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
50 50
51 51 del { text-decoration: line-through; }
52 52
53 53 abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
54 54
55 55 table { border-collapse: collapse; border-spacing: 0; }
56 56
57 57 hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
58 58
59 59 input, select { vertical-align: middle; }
60 60
61 61
62 62 /**
63 63 * Font normalization inspired by YUI Library's fonts.css: developer.yahoo.com/yui/
64 64 */
65 65
66 66 body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */
67 67 select, input, textarea, button { font:99% sans-serif; }
68 68
69 69 /* Normalize monospace sizing:
70 70 en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */
71 71 pre, code, kbd, samp { font-family: monospace, sans-serif; }
72 72
73 73
74 74 /**
75 75 * Primary styles
76 76 *
77 77 * Author: IPython Development Team
78 78 */
79 79
80 80
81 81 body {
82 82 background-color: white;
83 83 /* This makes sure that the body covers the entire window and needs to
84 84 be in a different element than the display: box in wrapper below */
85 85 position: absolute;
86 86 left: 0px;
87 87 right: 0px;
88 88 top: 0px;
89 89 bottom: 0px;
90 90 overflow: hidden;
91 91 }
92 92
93 93
94 94 div#header {
95 95 /* Initially hidden to prevent FLOUC */
96 96 display: none;
97 97 position: relative;
98 98 height: 45px;
99 99 padding: 5px;
100 100 margin: 0px;
101 101 width: 100%;
102 102 }
103 103
104 104 span#ipython_notebook {
105 105 position: absolute;
106 106 padding: 2px;
107 107 }
108 108
109 109 span#ipython_notebook h1 {
110 110 font-family: Verdana, "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
111 111 font-size: 197%;
112 112 display: inline;
113 113 }
114 114
115 115 span#save_widget {
116 116 position: absolute;
117 117 left: 0px;
118 118 padding: 5px 0px;
119 119 margin: 0px 0px 0px 0px;
120 120 }
121 121
122 122 input#notebook_name {
123 123 height: 1em;
124 124 line-height: 1em;
125 125 padding: 5px;
126 126 }
127 127
128 128 span#kernel_status {
129 129 position: absolute;
130 130 padding: 8px 5px 5px 5px;
131 131 right: 10px;
132 132 font-weight: bold;
133 133 }
134 134
135 135 .status_idle {
136 136 color: gray;
137 137 }
138 138
139 139 .status_busy {
140 140 color: red;
141 141 }
142 142
143 143 .status_restarting {
144 144 color: black;
145 145 }
146 146
147 147 div#notebook_app {
148 148 /* Initially hidden to prevent FLOUC */
149 149 display: none;
150 150 width: 100%;
151 151 position: relative;
152 152 }
153 153
154 154 div#left_panel {
155 155 overflow-y: auto;
156 156 top: 0px;
157 157 left: 0px;
158 158 margin: 0px;
159 159 padding: 0px;
160 160 position: absolute;
161 161 }
162 162
163 163 h3.section_header {
164 164 padding: 5px;
165 165 }
166 166
167 167 div.section_content {
168 168 padding: 5px;
169 169 }
170 170
171 171
172 172 span.section_row_buttons > button {
173 173 width: 60px;
174 174 }
175 175
176 176 .section_row {
177 177 margin: 5px 0px;
178 178 }
179 179
180 180 .section_row_buttons {
181 181 float: right;
182 182 }
183 183
184 184 .section_row_header {
185 185 float: left;
186 186 font-size: 85%;
187 187 padding: 0.2em 0em;
188 188 font-weight: bold;
189 189 }
190 190
191 191 span.button_label {
192 192 padding: 0.2em 1em;
193 193 font-size: 77%;
194 194 float: right;
195 195 }
196 196
197 197 /* This is needed because FF was adding a 2px margin top and bottom. */
198 198 .section_row .ui-button {
199 199 margin-top: 0px;
200 200 margin-bottom: 0px;
201 201 }
202 202
203 203
204 204 .ui-button .ui-button-text {
205 205 padding: 0.2em 0.8em;
206 206 font-size: 77%;
207 207 }
208 208
209 #download_format {
210 float: right;
211 font-size: 85%;
212 width: 60px;
213 margin: 1px 5px;
214 }
215
209 216 div#left_panel_splitter {
210 217 width: 8px;
211 218 top: 0px;
212 219 left: 202px;
213 220 margin: 0px;
214 221 padding: 0px;
215 222 position: absolute;
216 223 }
217 224
218 225 div#notebook_panel {
219 226 /* The L margin will be set in the Javascript code*/
220 227 margin: 0px 0px 0px 0px;
221 228 padding: 0px;
222 229 }
223 230
224 231 div#notebook {
225 232 overflow-y: scroll;
226 233 overflow-x: auto;
227 234 width: 100%;
228 235 padding: 0px 15px 0px 15px;
229 236 margin: 0px
230 237 background-color: white;
231 238 }
232 239
233 240 div#pager_splitter {
234 241 height: 8px;
235 242 }
236 243
237 244 div#pager {
238 245 padding: 15px;
239 246 overflow: auto;
240 247 }
241 248
242 249 div.cell {
243 250 width: 100%;
244 251 padding: 5px;
245 252 /* This acts as a spacer between cells, that is outside the border */
246 253 margin: 15px 0px 15px 0px;
247 254 }
248 255
249 256 div.code_cell {
250 257 background-color: white;
251 258 }
252 259
253 260 div.prompt {
254 261 width: 80px;
255 262 padding: 0.4em;
256 263 margin: 0px;
257 264 font-family: monospace;
258 265 }
259 266
260 267 div.input_prompt {
261 268 color: navy;
262 269 }
263 270
264 271 div.output {
265 272 /* This is a spacer between the input and output of each cell */
266 273 margin-top: 15px;
267 274 }
268 275
269 276 div.output_prompt {
270 277 color: darkred;
271 278 }
272 279
273 280 div.output_area {
274 281 text-align: left;
275 282 color: black;
276 283 font-family: monospace;
277 284 }
278 285
279 286 div.output_stream {
280 287 padding: 0.4em;
281 288 }
282 289
283 290 div.output_latex {
284 291 /* Slightly bigger than the rest of the notebook */
285 292 font-size: 116%;
286 293 }
287 294
288 295 div.output_html {
289 296 }
290 297
291 298 div.output_png {
292 299 }
293 300
294 301 div.text_cell {
295 302 background-color: white;
296 303 }
297 304
298 305 textarea.text_cell_input {
299 306 /* Slightly bigger than the rest of the notebook */
300 307 font-size: 116%;
301 308 font-family: monospace;
302 309 outline: none;
303 310 resize: none;
304 311 width: inherit;
305 312 border-style: none;
306 313 padding: 0px;
307 314 margin: 0px;
308 315 color: black;
309 316 }
310 317
311 318 div.text_cell_render {
312 319 font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
313 320 /* Slightly bigger than the rest of the notebook */
314 321 font-size: 116%;
315 322 outline: none;
316 323 resize: none;
317 324 width: inherit;
318 325 border-style: none;
319 326 padding: 5px;
320 327 color: black;
321 328 }
322 329
323 330 div.text_cell_render em {font-style: italic;}
324 331 div.text_cell_render strong {font-weight: bold;}
325 332 div.text_cell_render u {text-decoration: underline;}
326 333 div.text_cell_render :link { text-decoration: underline }
327 334 div.text_cell_render :visited { text-decoration: underline }
328 335 div.text_cell_render h1 {font-size: 197%; margin: .67em 0; font-weight: bold;}
329 336 div.text_cell_render h2 {font-size: 153.9%; margin: .75em 0; font-weight: bold;}
330 337 div.text_cell_render h3 {font-size: 116%; margin: .83em 0; font-weight: bold;}
331 338 div.text_cell_render h4 {margin: 1.12em 0; font-weight: bold;}
332 339 div.text_cell_render h5 {font-size: 85%.; margin: 1.5em 0; font-weight: bold;}
333 340 div.text_cell_render h6 {font-size: 77%; margin: 1.67em 0; font-weight: bold;}
334 341 div.text_cell_render ul {list-style:disc; margin-left: 40px;}
335 342 div.text_cell_render ul ul {list-style:square; margin-left: 40px;}
336 343 div.text_cell_render ul ul ul {list-style:circle; margin-left: 40px;}
337 344 div.text_cell_render ol {list-style:upper-roman; margin-left: 40px;}
338 345 div.text_cell_render ol ol {list-style:upper-alpha;}
339 346 div.text_cell_render ol ol ol {list-style:decimal;}
340 347 div.text_cell_render ol ol ol ol {list-style:lower-alpha;}
341 348 div.text_cell_render ol ol ol ol ol {list-style:lower-roman;}
342 349
343 350
344 351 .CodeMirror {
345 352 overflow: hidden; /* Changed from auto to remove scrollbar */
346 353 height: auto; /* Changed to auto to autogrow */
347 354 line-height: 1.231; /* Changed from 1em to our global default */
348 355 }
349 356
350 357 /* CSS font colors for translated ANSI colors. */
351 358
352 359
353 360 .ansiblack {color: black;}
354 361 .ansired {color: darkred;}
355 362 .ansigreen {color: darkgreen;}
356 363 .ansiyellow {color: brown;}
357 364 .ansiblue {color: darkblue;}
358 365 .ansipurple {color: darkviolet;}
359 366 .ansicyan {color: steelblue;}
360 367 .ansigrey {color: grey;}
361 368 .ansibold {font-weight: bold;}
362 369
363 370 .completions {
364 371 position: absolute;
365 372 z-index: 10;
366 373 overflow: auto;
367 374 border: 1px solid black;
368 375 }
369 376
370 377 .completions select {
371 378 background: white;
372 379 outline: none;
373 380 border: none;
374 381 padding: 0px;
375 382 margin: 0px;
376 383 font-family: monospace;
377 384 }
378 385
@@ -1,323 +1,333
1 1
2 2 //============================================================================
3 3 // CodeCell
4 4 //============================================================================
5 5
6 6 var IPython = (function (IPython) {
7 7
8 8 var utils = IPython.utils;
9 9
10 10 var CodeCell = function (notebook) {
11 11 this.code_mirror = null;
12 12 this.input_prompt_number = ' ';
13 13 this.is_completing = false;
14 14 this.completion_cursor = null;
15 15 IPython.Cell.apply(this, arguments);
16 16 };
17 17
18 18
19 19 CodeCell.prototype = new IPython.Cell();
20 20
21 21
22 22 CodeCell.prototype.create_element = function () {
23 23 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
24 24 var input = $('<div></div>').addClass('input hbox');
25 25 input.append($('<div/>').addClass('prompt input_prompt'));
26 26 var input_area = $('<div/>').addClass('input_area box-flex1');
27 27 this.code_mirror = CodeMirror(input_area.get(0), {
28 28 indentUnit : 4,
29 29 enterMode : 'flat',
30 30 tabMode: 'shift',
31 31 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
32 32 });
33 33 input.append(input_area);
34 34 var output = $('<div></div>').addClass('output vbox');
35 35 cell.append(input).append(output);
36 36 this.element = cell;
37 37 this.collapse()
38 38 };
39 39
40 40
41 41 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
42 42 // This method gets called in CodeMirror's onKeyDown/onKeyPress handlers and
43 43 // is used to provide custom key handling. Its return value is used to determine
44 44 // if CodeMirror should ignore the event: true = ignore, false = don't ignore.
45 45 if (event.keyCode === 13 && event.shiftKey) {
46 46 // Always ignore shift-enter in CodeMirror as we handle it.
47 47 return true;
48 48 // } else if (event.keyCode == 32 && (event.ctrlKey || event.metaKey) && !event.altKey) {
49 49 } else if (event.keyCode == 9) {
50 50 var cur = editor.getCursor();
51 51 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur).trim();
52 52 if (pre_cursor === "") {
53 53 // Don't autocomplete if the part of the line before the cursor is empty.
54 54 // In this case, let CodeMirror handle indentation.
55 55 return false;
56 56 } else {
57 57 // Autocomplete the current line.
58 58 event.stop();
59 59 var line = editor.getLine(cur.line);
60 60 this.is_completing = true;
61 61 this.completion_cursor = cur;
62 62 IPython.notebook.complete_cell(this, line, cur.ch);
63 63 return true;
64 64 }
65 65 } else {
66 66 if (this.is_completing && this.completion_cursor !== editor.getCursor()) {
67 67 this.is_completing = false;
68 68 this.completion_cursor = null;
69 69 }
70 70 return false;
71 71 };
72 72 };
73 73
74 74
75 75 CodeCell.prototype.finish_completing = function (matched_text, matches) {
76 76 if (!this.is_completing || matches.length === 0) {return;}
77 77 // console.log("Got matches", matched_text, matches);
78 78
79 79 var that = this;
80 80 var cur = this.completion_cursor;
81 81 var complete = $('<div/>').addClass('completions');
82 82 var select = $('<select/>').attr('multiple','true');
83 83 for (var i=0; i<matches.length; ++i) {
84 84 select.append($('<option/>').text(matches[i]));
85 85 }
86 86 select.children().first().attr('selected','true');
87 87 select.attr('size',Math.min(10,matches.length));
88 88 var pos = this.code_mirror.cursorCoords();
89 89 complete.css('left',pos.x+'px');
90 90 complete.css('top',pos.yBot+'px');
91 91 complete.append(select);
92 92
93 93 $('body').append(complete);
94 94 var done = false;
95 95
96 96 var insert = function (selected_text) {
97 97 that.code_mirror.replaceRange(
98 98 selected_text,
99 99 {line: cur.line, ch: (cur.ch-matched_text.length)},
100 100 {line: cur.line, ch: cur.ch}
101 101 );
102 102 };
103 103
104 104 var close = function () {
105 105 if (done) return;
106 106 done = true;
107 107 complete.remove();
108 108 that.is_completing = false;
109 109 that.completion_cursor = null;
110 110 };
111 111
112 112 var pick = function () {
113 113 insert(select.val()[0]);
114 114 close();
115 115 setTimeout(function(){that.code_mirror.focus();}, 50);
116 116 };
117 117
118 118 select.blur(close);
119 119 select.keydown(function (event) {
120 120 var code = event.which;
121 121 if (code === 13 || code === 32) {
122 122 // Pressing SPACE or ENTER will cause a pick
123 123 event.stopPropagation();
124 124 event.preventDefault();
125 125 pick();
126 126 } else if (code === 38 || code === 40) {
127 127 // We don't want the document keydown handler to handle UP/DOWN,
128 128 // but we want the default action.
129 129 event.stopPropagation();
130 130 } else {
131 131 // All other key presses simple exit completion.
132 132 event.stopPropagation();
133 133 event.preventDefault();
134 134 close();
135 135 that.code_mirror.focus();
136 136 }
137 137 });
138 138 // Double click also causes a pick.
139 139 select.dblclick(pick);
140 140 select.focus();
141 141 };
142 142
143 143
144 144 CodeCell.prototype.select = function () {
145 145 IPython.Cell.prototype.select.apply(this);
146 146 this.code_mirror.focus();
147 147 };
148 148
149 149
150 150 CodeCell.prototype.append_pyout = function (data, n) {
151 151 var toinsert = $("<div/>").addClass("output_area output_pyout hbox");
152 152 toinsert.append($('<div/>').
153 153 addClass('prompt output_prompt').
154 154 html('Out[' + n + ']:')
155 155 );
156 156 this.append_display_data(data, toinsert);
157 157 toinsert.children().last().addClass("box_flex1");
158 158 this.element.find("div.output").append(toinsert);
159 159 // If we just output latex, typeset it.
160 160 if (data["text/latex"] !== undefined) {
161 161 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
162 162 };
163 163 };
164 164
165 165
166 166 CodeCell.prototype.append_pyerr = function (ename, evalue, tb) {
167 167 var s = '';
168 168 var len = tb.length;
169 169 for (var i=0; i<len; i++) {
170 170 s = s + tb[i] + '\n';
171 171 }
172 172 s = s + '\n';
173 173 this.append_stream(s);
174 174 };
175 175
176 176
177 177 CodeCell.prototype.append_display_data = function (data, element) {
178 178 if (data["text/html"] !== undefined) {
179 179 this.append_html(data["text/html"], element);
180 180 } else if (data["text/latex"] !== undefined) {
181 181 this.append_latex(data["text/latex"], element);
182 182 // If it is undefined, then we just appended to div.output, which
183 183 // makes the latex visible and we can typeset it. The typesetting
184 184 // has to be done after the latex is on the page.
185 185 if (element === undefined) {
186 186 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
187 187 };
188 188 } else if (data["image/svg+xml"] !== undefined) {
189 189 this.append_svg(data["image/svg+xml"], element);
190 190 } else if (data["image/png"] !== undefined) {
191 191 this.append_png(data["image/png"], element);
192 192 } else if (data["text/plain"] !== undefined) {
193 193 this.append_stream(data["text/plain"], element);
194 194 };
195 195 return element;
196 196 };
197 197
198 198
199 199 CodeCell.prototype.append_html = function (html, element) {
200 200 element = element || this.element.find("div.output");
201 201 var toinsert = $("<div/>").addClass("output_area output_html");
202 202 toinsert.append(html);
203 203 element.append(toinsert);
204 204 return element;
205 205 }
206 206
207 207
208 208 CodeCell.prototype.append_stream = function (data, element) {
209 209 element = element || this.element.find("div.output");
210 210 var toinsert = $("<div/>").addClass("output_area output_stream");
211 211 toinsert.append($("<pre/>").html(utils.fixConsole(data)));
212 212 element.append(toinsert);
213 213 return element;
214 214 };
215 215
216 216
217 217 CodeCell.prototype.append_svg = function (svg, element) {
218 218 element = element || this.element.find("div.output");
219 219 var toinsert = $("<div/>").addClass("output_area output_svg");
220 220 toinsert.append(svg);
221 221 element.append(toinsert);
222 222 return element;
223 223 };
224 224
225 225
226 226 CodeCell.prototype.append_png = function (png, element) {
227 227 element = element || this.element.find("div.output");
228 228 var toinsert = $("<div/>").addClass("output_area output_png");
229 229 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
230 230 element.append(toinsert);
231 231 return element;
232 232 };
233 233
234 234
235 235 CodeCell.prototype.append_latex = function (latex, element) {
236 236 // This method cannot do the typesetting because the latex first has to
237 237 // be on the page.
238 238 element = element || this.element.find("div.output");
239 239 var toinsert = $("<div/>").addClass("output_area output_latex");
240 240 toinsert.append(latex);
241 241 element.append(toinsert);
242 242 return element;
243 243 }
244 244
245 245
246 246 CodeCell.prototype.clear_output = function () {
247 247 this.element.find("div.output").html("");
248 248 };
249 249
250 250
251 251 CodeCell.prototype.clear_input = function () {
252 252 this.code_mirror.setValue('');
253 253 };
254 254
255 255
256 256 CodeCell.prototype.collapse = function () {
257 257 this.element.find('div.output').hide();
258 258 };
259 259
260 260
261 261 CodeCell.prototype.expand = function () {
262 262 this.element.find('div.output').show();
263 263 };
264 264
265 265
266 266 CodeCell.prototype.set_input_prompt = function (number) {
267 267 var n = number || ' ';
268 268 this.input_prompt_number = n
269 269 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
270 270 };
271 271
272 272
273 273 CodeCell.prototype.get_code = function () {
274 274 return this.code_mirror.getValue();
275 275 };
276 276
277 277
278 278 CodeCell.prototype.set_code = function (code) {
279 279 return this.code_mirror.setValue(code);
280 280 };
281 281
282 282
283 283 CodeCell.prototype.at_top = function () {
284 284 var cursor = this.code_mirror.getCursor();
285 285 if (cursor.line === 0) {
286 286 return true;
287 287 } else {
288 288 return false;
289 289 }
290 290 };
291 291
292 292
293 293 CodeCell.prototype.at_bottom = function () {
294 294 var cursor = this.code_mirror.getCursor();
295 295 if (cursor.line === (this.code_mirror.lineCount()-1)) {
296 296 return true;
297 297 } else {
298 298 return false;
299 299 }
300 300 };
301 301
302 302
303 303 CodeCell.prototype.fromJSON = function (data) {
304 304 if (data.cell_type === 'code') {
305 this.set_code(data.code);
305 if (data.input !== undefined) {
306 this.set_code(data.input);
307 }
308 if (data.prompt_number !== undefined) {
306 309 this.set_input_prompt(data.prompt_number);
310 } else {
311 this.set_input_prompt();
312 };
307 313 };
308 314 };
309 315
310 316
311 317 CodeCell.prototype.toJSON = function () {
312 return {
313 code : this.get_code(),
314 cell_type : 'code',
315 prompt_number : this.input_prompt_number
318 var data = {}
319 data.input = this.get_code();
320 data.cell_type = 'code';
321 if (this.input_prompt_number !== ' ') {
322 data.prompt_number = this.input_prompt_number
316 323 };
324 data.outputs = [];
325 data.language = 'python';
326 return data;
317 327 };
318 328
319 329 IPython.CodeCell = CodeCell;
320 330
321 331 return IPython;
322 332 }(IPython));
323 333
@@ -1,624 +1,606
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 this.filename = null;
18 this.notebook_load_re = /%notebook load/
19 this.notebook_save_re = /%notebook save/
20 this.notebook_filename_re = /(\w)+.ipynb/
21 17 this.style();
22 18 this.create_elements();
23 19 this.bind_events();
24 this.start_kernel();
25 20 };
26 21
27 22
28 23 Notebook.prototype.style = function () {
29 24 $('div#notebook').addClass('border-box-sizing');
30 25 };
31 26
32 27
33 28 Notebook.prototype.create_elements = function () {
34 29 // We add this end_space div to the end of the notebook div to:
35 30 // i) provide a margin between the last cell and the end of the notebook
36 31 // ii) to prevent the div from scrolling up when the last cell is being
37 32 // edited, but is too low on the page, which browsers will do automatically.
38 33 this.element.append($('<div class="end_space"></div>').height(50));
39 34 $('div#notebook').addClass('border-box-sizing');
40 35 };
41 36
42 37
43 38 Notebook.prototype.bind_events = function () {
44 39 var that = this;
45 40 $(document).keydown(function (event) {
46 41 // console.log(event);
47 42 if (event.which === 38) {
48 43 var cell = that.selected_cell();
49 44 if (cell.at_top()) {
50 45 event.preventDefault();
51 46 that.select_prev();
52 47 };
53 48 } else if (event.which === 40) {
54 49 var cell = that.selected_cell();
55 50 if (cell.at_bottom()) {
56 51 event.preventDefault();
57 52 that.select_next();
58 53 };
59 54 } else if (event.which === 13 && event.shiftKey) {
60 55 that.execute_selected_cell();
61 56 return false;
62 57 } else if (event.which === 13 && event.ctrlKey) {
63 58 that.execute_selected_cell({terminal:true});
64 59 return false;
65 60 };
66 61 });
67 62
68 63 this.element.bind('collapse_pager', function () {
69 64 var app_height = $('div#notebook_app').height(); // content height
70 65 var splitter_height = $('div#pager_splitter').outerHeight(true);
71 66 var new_height = app_height - splitter_height;
72 67 that.element.animate({height : new_height + 'px'}, 'fast');
73 68 });
74 69
75 70 this.element.bind('expand_pager', function () {
76 71 var app_height = $('div#notebook_app').height(); // content height
77 72 var splitter_height = $('div#pager_splitter').outerHeight(true);
78 73 var pager_height = $('div#pager').outerHeight(true);
79 74 var new_height = app_height - pager_height - splitter_height;
80 75 that.element.animate({height : new_height + 'px'}, 'fast');
81 76 });
82 77
83 78 this.element.bind('collapse_left_panel', function () {
84 79 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
85 80 var new_margin = splitter_width;
86 81 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
87 82 });
88 83
89 84 this.element.bind('expand_left_panel', function () {
90 85 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
91 86 var left_panel_width = IPython.left_panel.width;
92 87 var new_margin = splitter_width + left_panel_width;
93 88 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
94 89 });
95 90 };
96 91
97 92
98 93 Notebook.prototype.scroll_to_bottom = function () {
99 94 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
100 95 };
101 96
102 97 // Cell indexing, retrieval, etc.
103 98
104 99
105 100 Notebook.prototype.cell_elements = function () {
106 101 return this.element.children("div.cell");
107 102 }
108 103
109 104
110 105 Notebook.prototype.ncells = function (cell) {
111 106 return this.cell_elements().length;
112 107 }
113 108
114 109
115 110 // TODO: we are often calling cells as cells()[i], which we should optimize
116 111 // to cells(i) or a new method.
117 112 Notebook.prototype.cells = function () {
118 113 return this.cell_elements().toArray().map(function (e) {
119 114 return $(e).data("cell");
120 115 });
121 116 }
122 117
123 118
124 119 Notebook.prototype.find_cell_index = function (cell) {
125 120 var result = null;
126 121 this.cell_elements().filter(function (index) {
127 122 if ($(this).data("cell") === cell) {
128 123 result = index;
129 124 };
130 125 });
131 126 return result;
132 127 };
133 128
134 129
135 130 Notebook.prototype.index_or_selected = function (index) {
136 131 return index || this.selected_index() || 0;
137 132 }
138 133
139 134
140 135 Notebook.prototype.select = function (index) {
141 136 if (index !== undefined && index >= 0 && index < this.ncells()) {
142 137 if (this.selected_index() !== null) {
143 138 this.selected_cell().unselect();
144 139 };
145 140 this.cells()[index].select();
146 141 if (index === (this.ncells()-1)) {
147 142 this.scroll_to_bottom();
148 143 };
149 144 };
150 145 return this;
151 146 };
152 147
153 148
154 149 Notebook.prototype.select_next = function () {
155 150 var index = this.selected_index();
156 151 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
157 152 this.select(index+1);
158 153 };
159 154 return this;
160 155 };
161 156
162 157
163 158 Notebook.prototype.select_prev = function () {
164 159 var index = this.selected_index();
165 160 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
166 161 this.select(index-1);
167 162 };
168 163 return this;
169 164 };
170 165
171 166
172 167 Notebook.prototype.selected_index = function () {
173 168 var result = null;
174 169 this.cell_elements().filter(function (index) {
175 170 if ($(this).data("cell").selected === true) {
176 171 result = index;
177 172 };
178 173 });
179 174 return result;
180 175 };
181 176
182 177
183 178 Notebook.prototype.cell_for_msg = function (msg_id) {
184 179 var cell_id = this.msg_cell_map[msg_id];
185 180 var result = null;
186 181 this.cell_elements().filter(function (index) {
187 182 cell = $(this).data("cell");
188 183 if (cell.cell_id === cell_id) {
189 184 result = cell;
190 185 };
191 186 });
192 187 return result;
193 188 };
194 189
195 190
196 191 Notebook.prototype.selected_cell = function () {
197 192 return this.cell_elements().eq(this.selected_index()).data("cell");
198 193 }
199 194
200 195
201 196 // Cell insertion, deletion and moving.
202 197
203 198
204 199 Notebook.prototype.delete_cell = function (index) {
205 200 var i = index || this.selected_index();
206 201 if (i !== null && i >= 0 && i < this.ncells()) {
207 202 this.cell_elements().eq(i).remove();
208 203 if (i === (this.ncells())) {
209 204 this.select(i-1);
210 205 } else {
211 206 this.select(i);
212 207 };
213 208 };
214 209 return this;
215 210 };
216 211
217 212
218 213 Notebook.prototype.append_cell = function (cell) {
219 214 this.element.find('div.end_space').before(cell.element);
220 215 return this;
221 216 };
222 217
223 218
224 219 Notebook.prototype.insert_cell_after = function (cell, index) {
225 220 var ncells = this.ncells();
226 221 if (ncells === 0) {
227 222 this.append_cell(cell);
228 223 return this;
229 224 };
230 225 if (index >= 0 && index < ncells) {
231 226 this.cell_elements().eq(index).after(cell.element);
232 227 };
233 228 return this
234 229 };
235 230
236 231
237 232 Notebook.prototype.insert_cell_before = function (cell, index) {
238 233 var ncells = this.ncells();
239 234 if (ncells === 0) {
240 235 this.append_cell(cell);
241 236 return this;
242 237 };
243 238 if (index >= 0 && index < ncells) {
244 239 this.cell_elements().eq(index).before(cell.element);
245 240 };
246 241 return this;
247 242 };
248 243
249 244
250 245 Notebook.prototype.move_cell_up = function (index) {
251 246 var i = index || this.selected_index();
252 247 if (i !== null && i < this.ncells() && i > 0) {
253 248 var pivot = this.cell_elements().eq(i-1);
254 249 var tomove = this.cell_elements().eq(i);
255 250 if (pivot !== null && tomove !== null) {
256 251 tomove.detach();
257 252 pivot.before(tomove);
258 253 this.select(i-1);
259 254 };
260 255 };
261 256 return this;
262 257 }
263 258
264 259
265 260 Notebook.prototype.move_cell_down = function (index) {
266 261 var i = index || this.selected_index();
267 262 if (i !== null && i < (this.ncells()-1) && i >= 0) {
268 263 var pivot = this.cell_elements().eq(i+1)
269 264 var tomove = this.cell_elements().eq(i)
270 265 if (pivot !== null && tomove !== null) {
271 266 tomove.detach();
272 267 pivot.after(tomove);
273 268 this.select(i+1);
274 269 };
275 270 };
276 271 return this;
277 272 }
278 273
279 274
280 275 Notebook.prototype.sort_cells = function () {
281 276 var ncells = this.ncells();
282 277 var sindex = this.selected_index();
283 278 var swapped;
284 279 do {
285 280 swapped = false
286 281 for (var i=1; i<ncells; i++) {
287 282 current = this.cell_elements().eq(i).data("cell");
288 283 previous = this.cell_elements().eq(i-1).data("cell");
289 284 if (previous.input_prompt_number > current.input_prompt_number) {
290 285 this.move_cell_up(i);
291 286 swapped = true;
292 287 };
293 288 };
294 289 } while (swapped);
295 290 this.select(sindex);
296 291 return this;
297 292 };
298 293
299 294
300 295 Notebook.prototype.insert_code_cell_before = function (index) {
301 296 // TODO: Bounds check for i
302 297 var i = this.index_or_selected(index);
303 298 var cell = new IPython.CodeCell(this);
304 299 // cell.set_input_prompt(this.next_prompt_number);
305 300 cell.set_input_prompt();
306 301 this.next_prompt_number = this.next_prompt_number + 1;
307 302 this.insert_cell_before(cell, i);
308 303 this.select(this.find_cell_index(cell));
309 304 return this;
310 305 }
311 306
312 307
313 308 Notebook.prototype.insert_code_cell_after = function (index) {
314 309 // TODO: Bounds check for i
315 310 var i = this.index_or_selected(index);
316 311 var cell = new IPython.CodeCell(this);
317 312 // cell.set_input_prompt(this.next_prompt_number);
318 313 cell.set_input_prompt();
319 314 this.next_prompt_number = this.next_prompt_number + 1;
320 315 this.insert_cell_after(cell, i);
321 316 this.select(this.find_cell_index(cell));
322 317 return this;
323 318 }
324 319
325 320
326 321 Notebook.prototype.insert_text_cell_before = function (index) {
327 322 // TODO: Bounds check for i
328 323 var i = this.index_or_selected(index);
329 324 var cell = new IPython.TextCell(this);
330 325 cell.config_mathjax();
331 326 this.insert_cell_before(cell, i);
332 327 this.select(this.find_cell_index(cell));
333 328 return this;
334 329 }
335 330
336 331
337 332 Notebook.prototype.insert_text_cell_after = function (index) {
338 333 // TODO: Bounds check for i
339 334 var i = this.index_or_selected(index);
340 335 var cell = new IPython.TextCell(this);
341 336 cell.config_mathjax();
342 337 this.insert_cell_after(cell, i);
343 338 this.select(this.find_cell_index(cell));
344 339 return this;
345 340 }
346 341
347 342
348 343 Notebook.prototype.text_to_code = function (index) {
349 344 // TODO: Bounds check for i
350 345 var i = this.index_or_selected(index);
351 346 var source_element = this.cell_elements().eq(i);
352 347 var source_cell = source_element.data("cell");
353 348 if (source_cell instanceof IPython.TextCell) {
354 349 this.insert_code_cell_after(i);
355 350 var target_cell = this.cells()[i+1];
356 351 target_cell.set_code(source_cell.get_text());
357 352 source_element.remove();
358 353 };
359 354 };
360 355
361 356
362 357 Notebook.prototype.code_to_text = function (index) {
363 358 // TODO: Bounds check for i
364 359 var i = this.index_or_selected(index);
365 360 var source_element = this.cell_elements().eq(i);
366 361 var source_cell = source_element.data("cell");
367 362 if (source_cell instanceof IPython.CodeCell) {
368 363 this.insert_text_cell_after(i);
369 364 var target_cell = this.cells()[i+1];
370 365 var text = source_cell.get_code();
371 366 if (text === "") {text = target_cell.placeholder;};
372 367 target_cell.set_text(text);
373 368 source_element.remove();
374 369 target_cell.edit();
375 370 };
376 371 };
377 372
378 373
379 374 // Cell collapsing
380 375
381 376 Notebook.prototype.collapse = function (index) {
382 377 var i = this.index_or_selected(index);
383 378 this.cells()[i].collapse();
384 379 };
385 380
386 381
387 382 Notebook.prototype.expand = function (index) {
388 383 var i = this.index_or_selected(index);
389 384 this.cells()[i].expand();
390 385 };
391 386
392 387
393 388 // Kernel related things
394 389
395 390 Notebook.prototype.start_kernel = function () {
396 391 this.kernel = new IPython.Kernel();
397 392 this.kernel.start_kernel($.proxy(this.kernel_started, this));
398 393 };
399 394
400 395
401 396 Notebook.prototype.handle_shell_reply = function (e) {
402 397 reply = $.parseJSON(e.data);
403 398 var header = reply.header;
404 399 var content = reply.content;
405 400 var msg_type = header.msg_type;
406 401 // console.log(reply);
407 402 var cell = this.cell_for_msg(reply.parent_header.msg_id);
408 403 if (msg_type === "execute_reply") {
409 404 cell.set_input_prompt(content.execution_count);
410 405 } else if (msg_type === "complete_reply") {
411 406 cell.finish_completing(content.matched_text, content.matches);
412 407 };
413 408 var payload = content.payload || [];
414 409 this.handle_payload(payload);
415 410 };
416 411
417 412
418 413 Notebook.prototype.handle_payload = function (payload) {
419 414 var l = payload.length;
420 415 if (l > 0) {
421 416 IPython.pager.clear();
422 417 IPython.pager.expand();
423 418 };
424 419 for (var i=0; i<l; i++) {
425 420 IPython.pager.append_text(payload[i].text);
426 421 };
427 422 };
428 423
429 424
430 425 Notebook.prototype.handle_iopub_reply = function (e) {
431 426 reply = $.parseJSON(e.data);
432 427 var content = reply.content;
433 428 // console.log(reply);
434 429 var msg_type = reply.header.msg_type;
435 430 var cell = this.cell_for_msg(reply.parent_header.msg_id);
436 431 if (msg_type === "stream") {
437 432 cell.expand();
438 433 cell.append_stream(content.data + "\n");
439 434 } else if (msg_type === "display_data") {
440 435 cell.expand();
441 436 cell.append_display_data(content.data);
442 437 } else if (msg_type === "pyout") {
443 438 cell.expand();
444 439 cell.append_pyout(content.data, content.execution_count)
445 440 } else if (msg_type === "pyerr") {
446 441 cell.expand();
447 442 cell.append_pyerr(content.ename, content.evalue, content.traceback);
448 443 } else if (msg_type === "status") {
449 444 if (content.execution_state === "busy") {
450 445 IPython.kernel_status_widget.status_busy();
451 446 } else if (content.execution_state === "idle") {
452 447 IPython.kernel_status_widget.status_idle();
453 448 };
454 449 }
455 450 };
456 451
457 452
458 453 Notebook.prototype.kernel_started = function () {
459 454 console.log("Kernel started: ", this.kernel.kernel_id);
460 455 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
461 456 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
462 457 };
463 458
464 459
465 460 Notebook.prototype.execute_selected_cell = function (options) {
466 461 // add_new: should a new cell be added if we are at the end of the nb
467 462 // terminal: execute in terminal mode, which stays in the current cell
468 463 default_options = {terminal: false, add_new: true}
469 464 $.extend(default_options, options)
470 465 var that = this;
471 466 var cell = that.selected_cell();
472 467 var cell_index = that.find_cell_index(cell);
473 468 if (cell instanceof IPython.CodeCell) {
474 469 cell.clear_output();
475 470 var code = cell.get_code();
476 if (that.notebook_load_re.test(code)) {
477 // %notebook load
478 var code_parts = code.split(' ');
479 if (code_parts.length === 3) {
480 that.load_notebook(code_parts[2]);
481 };
482 } else if (that.notebook_save_re.test(code)) {
483 // %notebook save
484 var code_parts = code.split(' ');
485 if (code_parts.length === 3) {
486 that.save_notebook(code_parts[2]);
487 } else {
488 that.save_notebook()
489 };
490 } else {
491 471 var msg_id = that.kernel.execute(cell.get_code());
492 472 that.msg_cell_map[msg_id] = cell.cell_id;
493 };
494 473 } else if (cell instanceof IPython.TextCell) {
495 474 cell.render();
496 475 }
497 476 if (default_options.terminal) {
498 477 cell.clear_input();
499 478 } else {
500 479 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
501 480 that.insert_code_cell_after();
502 481 // If we are adding a new cell at the end, scroll down to show it.
503 482 that.scroll_to_bottom();
504 483 } else {
505 484 that.select(cell_index+1);
506 485 };
507 486 };
508 487 };
509 488
510 489
511 490 Notebook.prototype.execute_all_cells = function () {
512 491 var ncells = this.ncells();
513 492 for (var i=0; i<ncells; i++) {
514 493 this.select(i);
515 494 this.execute_selected_cell({add_new:false});
516 495 };
517 496 this.scroll_to_bottom();
518 497 };
519 498
520 499
521 500 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
522 501 var msg_id = this.kernel.complete(line, cursor_pos);
523 502 this.msg_cell_map[msg_id] = cell.cell_id;
524 503 };
525 504
526 505 // Persistance and loading
527 506
528 507
529 508 Notebook.prototype.fromJSON = function (data) {
530 509 var ncells = this.ncells();
531 510 for (var i=0; i<ncells; i++) {
532 511 // Always delete cell 0 as they get renumbered as they are deleted.
533 512 this.delete_cell(0);
534 513 };
535 var new_cells = data.cells;
514 // Only handle 1 worksheet for now.
515 var worksheet = data.worksheets[0];
516 if (worksheet !== undefined) {
517 var new_cells = worksheet.cells;
536 518 ncells = new_cells.length;
537 519 var cell_data = null;
538 520 for (var i=0; i<ncells; i++) {
539 521 cell_data = new_cells[i];
540 522 if (cell_data.cell_type == 'code') {
541 523 this.insert_code_cell_after();
542 524 this.selected_cell().fromJSON(cell_data);
543 525 } else if (cell_data.cell_type === 'text') {
544 526 this.insert_text_cell_after();
545 527 this.selected_cell().fromJSON(cell_data);
546 528 };
547 529 };
548 530 };
531 };
549 532
550 533
551 534 Notebook.prototype.toJSON = function () {
552 535 var cells = this.cells();
553 536 var ncells = cells.length;
554 537 cell_array = new Array(ncells);
555 538 for (var i=0; i<ncells; i++) {
556 539 cell_array[i] = cells[i].toJSON();
557 540 };
558 json = {
559 cells : cell_array
560 };
561 return json
562 };
563
564
565 Notebook.prototype.test_filename = function (filename) {
566 if (this.notebook_filename_re.test(filename)) {
567 return true;
568 } else {
569 var bad_filename = $('<div/>');
570 bad_filename.html(
571 "The filename you entered (" + filename + ") is not valid. Notebook filenames must have the following form: foo.ipynb"
572 );
573 bad_filename.dialog({title: 'Invalid filename', modal: true});
574 return false;
575 };
576 };
577
578 Notebook.prototype.save_notebook = function (filename) {
579 this.filename = filename || this.filename || '';
580 if (this.filename === '') {
581 var no_filename = $('<div/>');
582 no_filename.html(
583 "This notebook has no filename, please specify a filename of the form: foo.ipynb"
584 );
585 no_filename.dialog({title: 'Missing filename', modal: true});
586 return;
541 data = {
542 // Only handle 1 worksheet for now.
543 worksheets : [{cells:cell_array}]
587 544 }
588 if (!this.test_filename(this.filename)) {return;}
589 var thedata = this.toJSON();
545 return data
546 };
547
548 Notebook.prototype.save_notebook = function () {
549 if (IPython.save_widget.test_notebook_name()) {
550 var notebook_id = IPython.save_widget.get_notebook_id();
551 var nbname = IPython.save_widget.get_notebook_name();
552 // We may want to move the name/id/nbformat logic inside toJSON?
553 var data = this.toJSON();
554 data.name = nbname;
555 data.nbformat = 2;
556 data.id = notebook_id
557 // We do the call with settings so we can set cache to false.
590 558 var settings = {
591 559 processData : false,
592 560 cache : false,
593 561 type : "PUT",
594 data : JSON.stringify(thedata),
595 success : function (data, status, xhr) {console.log(data);}
562 data : JSON.stringify(data),
563 success : $.proxy(this.notebook_saved,this)
564 };
565 IPython.save_widget.status_saving();
566 $.ajax("/notebooks/" + notebook_id, settings);
596 567 };
597 $.ajax("/notebooks/" + this.filename, settings);
598 568 };
599 569
600 570
601 Notebook.prototype.load_notebook = function (filename) {
602 if (!this.test_filename(filename)) {return;}
603 var that = this;
571 Notebook.prototype.notebook_saved = function (data, status, xhr) {
572 IPython.save_widget.status_save();
573 }
574
575
576 Notebook.prototype.load_notebook = function () {
577 var notebook_id = IPython.save_widget.get_notebook_id();
604 578 // We do the call with settings so we can set cache to false.
605 579 var settings = {
606 580 processData : false,
607 581 cache : false,
608 582 type : "GET",
609 583 dataType : "json",
610 success : function (data, status, xhr) {
611 that.fromJSON(data);
612 that.filename = filename;
613 that.kernel.restart();
614 }
584 success : $.proxy(this.notebook_loaded,this)
615 585 };
616 $.ajax("/notebooks/" + filename, settings);
586 IPython.save_widget.status_loading();
587 $.ajax("/notebooks/" + notebook_id, settings);
617 588 }
618 589
590
591 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
592 this.fromJSON(data);
593 if (this.ncells() === 0) {
594 this.insert_code_cell_after();
595 };
596 IPython.save_widget.status_save();
597 IPython.save_widget.set_notebook_name(data.name);
598 this.start_kernel();
599 };
600
619 601 IPython.Notebook = Notebook;
620 602
621 603 return IPython;
622 604
623 605 }(IPython));
624 606
@@ -1,43 +1,47
1 1
2 2 //============================================================================
3 3 // On document ready
4 4 //============================================================================
5 5
6 6
7 7 $(document).ready(function () {
8 8
9 9 MathJax.Hub.Config({
10 10 tex2jax: {
11 11 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
12 12 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
13 13 },
14 14 displayAlign: 'left', // Change this to 'center' to center equations.
15 15 "HTML-CSS": {
16 16 styles: {'.MathJax_Display': {"margin": 0}}
17 17 }
18 18 });
19 19
20 20 $('div#header').addClass('border-box-sizing');
21 21 $('div#notebook_app').addClass('border-box-sizing ui-widget ui-widget-content');
22 22 $('div#notebook_panel').addClass('border-box-sizing ui-widget');
23 23
24 24 IPython.layout_manager = new IPython.LayoutManager();
25 25 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
26 26 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
27 27 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
28 28 IPython.notebook = new IPython.Notebook('div#notebook');
29 29 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
30 30 IPython.kernel_status_widget.status_idle();
31 31
32 32 IPython.layout_manager.do_resize();
33 IPython.notebook.insert_code_cell_after();
34 IPython.layout_manager.do_resize();
35 33
36 34 // These have display: none in the css file and are made visible here to prevent FLOUC.
37 35 $('div#header').css('display','block');
38 36 $('div#notebook_app').css('display','block');
37
38 IPython.notebook.load_notebook();
39
40 // Perform these actions after the notebook has been loaded.
41 setTimeout(function () {
42 IPython.save_widget.update_url();
39 43 IPython.layout_manager.do_resize();
40 44 IPython.pager.collapse();
41 IPython.layout_manager.do_resize();
45 }, 100);
42 46 });
43 47
@@ -1,225 +1,236
1 1
2 2 //============================================================================
3 3 // Cell
4 4 //============================================================================
5 5
6 6 var IPython = (function (IPython) {
7 7
8 8 var utils = IPython.utils;
9 9
10 10 // Base PanelSection class
11 11
12 12 var PanelSection = function (selector) {
13 13 this.selector = selector;
14 14 if (this.selector !== undefined) {
15 15 this.element = $(selector);
16 16 this.header = this.element.find('h3.section_header');
17 17 this.content = this.element.find('div.section_content');
18 18 this.style();
19 19 this.bind_events();
20 20 }
21 21 this.expanded = true;
22 22 };
23 23
24 24
25 25 PanelSection.prototype.style = function () {
26 26 this.header.addClass('ui-widget ui-state-default');
27 27 this.content.addClass('ui-widget section_content');
28 28 };
29 29
30 30
31 31 PanelSection.prototype.bind_events = function () {
32 32 var that = this;
33 33 this.header.click(function () {
34 34 that.toggle();
35 35 });
36 36 this.header.hover(function () {
37 37 that.header.toggleClass('ui-state-hover');
38 38 });
39 39 };
40 40
41 41
42 42 PanelSection.prototype.expand = function () {
43 43 if (!this.expanded) {
44 44 this.content.slideDown('fast');
45 45 this.expanded = true;
46 46 };
47 47 };
48 48
49 49
50 50 PanelSection.prototype.collapse = function () {
51 51 if (this.expanded) {
52 52 this.content.slideUp('fast');
53 53 this.expanded = false;
54 54 };
55 55 };
56 56
57 57
58 58 PanelSection.prototype.toggle = function () {
59 59 if (this.expanded === true) {
60 60 this.collapse();
61 61 } else {
62 62 this.expand();
63 63 };
64 64 };
65 65
66 66
67 67 PanelSection.prototype.create_children = function () {};
68 68
69 69
70 70 // NotebookSection
71 71
72 72 var NotebookSection = function () {
73 73 PanelSection.apply(this, arguments);
74 74 };
75 75
76 76
77 77 NotebookSection.prototype = new PanelSection();
78 78
79 79
80 80 NotebookSection.prototype.style = function () {
81 81 PanelSection.prototype.style.apply(this);
82 82 this.content.addClass('ui-helper-clearfix');
83 83 this.content.find('div.section_row').addClass('ui-helper-clearfix');
84 84 this.content.find('#new_open').buttonset();
85 this.content.find('#download_notebook').button();
86 this.content.find('#upload_notebook').button();
87 this.content.find('#download_format').addClass('ui-widget ui-widget-content');
88 this.content.find('#download_format option').addClass('ui-widget ui-widget-content');
85 89 };
86 90
87 91
88 92 NotebookSection.prototype.bind_events = function () {
89 93 PanelSection.prototype.bind_events.apply(this);
94 var that = this;
90 95 this.content.find('#new_notebook').click(function () {
91 alert('Not Implemented');
96 console.log('click!')
97 window.open('/');
92 98 });
93 99 this.content.find('#open_notebook').click(function () {
94 100 alert('Not Implemented');
95 101 });
102 this.content.find('#download_notebook').click(function () {
103 var format = that.content.find('#download_format').val();
104 var notebook_id = IPython.save_widget.get_notebook_id();
105 var url = '/notebooks/' + notebook_id + '?format=' + format;
106 window.open(url,'_newtab');
107 });
96 108 };
97 109
98
99 110 // CellSection
100 111
101 112 var CellSection = function () {
102 113 PanelSection.apply(this, arguments);
103 114 };
104 115
105 116
106 117 CellSection.prototype = new PanelSection();
107 118
108 119
109 120 CellSection.prototype.style = function () {
110 121 PanelSection.prototype.style.apply(this);
111 122 this.content.addClass('ui-helper-clearfix');
112 123 this.content.find('div.section_row').addClass('ui-helper-clearfix');
113 124 this.content.find('#delete_cell').button();
114 125 this.content.find('#insert').buttonset();
115 126 this.content.find('#move').buttonset();
116 127 this.content.find('#cell_type').buttonset();
117 128 this.content.find('#toggle_output').buttonset();
118 129 this.content.find('#run_cells').buttonset();
119 130 };
120 131
121 132
122 133 CellSection.prototype.bind_events = function () {
123 134 PanelSection.prototype.bind_events.apply(this);
124 135 this.content.find('#collapse_cell').click(function () {
125 136 IPython.notebook.collapse();
126 137 });
127 138 this.content.find('#expand_cell').click(function () {
128 139 IPython.notebook.expand();
129 140 });
130 141 this.content.find('#delete_cell').click(function () {
131 142 IPython.notebook.delete_cell();
132 143 });
133 144 this.content.find('#insert_cell_above').click(function () {
134 145 IPython.notebook.insert_code_cell_before();
135 146 });
136 147 this.content.find('#insert_cell_below').click(function () {
137 148 IPython.notebook.insert_code_cell_after();
138 149 });
139 150 this.content.find('#move_cell_up').click(function () {
140 151 IPython.notebook.move_cell_up();
141 152 });
142 153 this.content.find('#move_cell_down').click(function () {
143 154 IPython.notebook.move_cell_down();
144 155 });
145 156 this.content.find('#to_code').click(function () {
146 157 IPython.notebook.text_to_code();
147 158 });
148 159 this.content.find('#to_text').click(function () {
149 160 IPython.notebook.code_to_text();
150 161 });
151 162 this.content.find('#run_selected_cell').click(function () {
152 163 IPython.notebook.execute_selected_cell();
153 164 });
154 165 this.content.find('#run_all_cells').click(function () {
155 166 IPython.notebook.execute_all_cells();
156 167 });
157 168 };
158 169
159 170
160 171 // KernelSection
161 172
162 173 var KernelSection = function () {
163 174 PanelSection.apply(this, arguments);
164 175 };
165 176
166 177
167 178 KernelSection.prototype = new PanelSection();
168 179
169 180
170 181 KernelSection.prototype.style = function () {
171 182 PanelSection.prototype.style.apply(this);
172 183 this.content.addClass('ui-helper-clearfix');
173 184 this.content.find('div.section_row').addClass('ui-helper-clearfix');
174 185 this.content.find('#int_restart').buttonset();
175 186 };
176 187
177 188
178 189 KernelSection.prototype.bind_events = function () {
179 190 PanelSection.prototype.bind_events.apply(this);
180 191 this.content.find('#restart_kernel').click(function () {
181 192 IPython.notebook.kernel.restart();
182 193 });
183 194 this.content.find('#int_kernel').click(function () {
184 195 IPython.notebook.kernel.interrupt();
185 196 });
186 197 };
187 198
188 199
189 200 // HelpSection
190 201
191 202 var HelpSection = function () {
192 203 PanelSection.apply(this, arguments);
193 204 };
194 205
195 206
196 207 HelpSection.prototype = new PanelSection();
197 208
198 209
199 210 HelpSection.prototype.style = function () {
200 211 PanelSection.prototype.style.apply(this);
201 212 PanelSection.prototype.style.apply(this);
202 213 this.content.addClass('ui-helper-clearfix');
203 214 this.content.find('div.section_row').addClass('ui-helper-clearfix');
204 215 this.content.find('#help_buttons0').buttonset();
205 216 this.content.find('#help_buttons1').buttonset();
206 217 };
207 218
208 219
209 220 HelpSection.prototype.bind_events = function () {
210 221 PanelSection.prototype.bind_events.apply(this);
211 222 };
212 223
213 224
214 225 // Set module variables
215 226
216 227 IPython.PanelSection = PanelSection;
217 228 IPython.NotebookSection = NotebookSection;
218 229 IPython.CellSection = CellSection;
219 230 IPython.KernelSection = KernelSection;
220 231 IPython.HelpSection = HelpSection;
221 232
222 233 return IPython;
223 234
224 235 }(IPython));
225 236
@@ -1,52 +1,101
1 1
2 2 //============================================================================
3 3 // Cell
4 4 //============================================================================
5 5
6 6 var IPython = (function (IPython) {
7 7
8 8 var utils = IPython.utils;
9 9
10 10 var SaveWidget = function (selector) {
11 11 this.selector = selector;
12 this.notebook_name_re = /[^/\\]+/
12 13 if (this.selector !== undefined) {
13 14 this.element = $(selector);
14 15 this.style();
15 16 this.bind_events();
16 17 }
17 18 };
18 19
19 20
20 21 SaveWidget.prototype.style = function () {
21 22 this.element.find('input#notebook_name').addClass('ui-widget ui-widget-content');
22 23 this.element.find('button#save_notebook').button();
23 24 var left_panel_width = $('div#left_panel').outerWidth();
24 25 var left_panel_splitter_width = $('div#left_panel_splitter').outerWidth();
25 26 $('span#save_widget').css({marginLeft:left_panel_width+left_panel_splitter_width});
26 27 };
27 28
28 29
29 30 SaveWidget.prototype.bind_events = function () {
30 31 var that = this;
31 32 this.element.find('button#save_notebook').click(function () {
32 IPython.notebook.save_notebook(that.get_notebook_name());
33 IPython.notebook.save_notebook();
33 34 });
34 35 };
35 36
36 37
37 38 SaveWidget.prototype.get_notebook_name = function () {
38 39 return this.element.find('input#notebook_name').attr('value');
39 40 }
40 41
41 42
42 SaveWidget.prototype.set_notebook_name = function (name) {
43 this.element.find('input#notebook_name').attr('value',name);
43 SaveWidget.prototype.set_notebook_name = function (nbname) {
44 this.element.find('input#notebook_name').attr('value',nbname);
44 45 }
45 46
46 47
48 SaveWidget.prototype.get_notebook_id = function () {
49 return this.element.find('span#notebook_id').text()
50 };
51
52
53 SaveWidget.prototype.update_url = function () {
54 var notebook_id = this.get_notebook_id();
55 if (notebook_id !== '') {
56 window.history.replaceState({}, '', notebook_id);
57 };
58 };
59
60
61 SaveWidget.prototype.test_notebook_name = function () {
62 var nbname = this.get_notebook_name();
63 if (this.notebook_name_re.test(nbname)) {
64 return true;
65 } else {
66 var bad_name = $('<div/>');
67 bad_name.html(
68 "The notebook name you entered (" +
69 nbname +
70 ") is not valid. Notebook names can contain any characters except / and \\"
71 );
72 bad_name.dialog({title: 'Invalid name', modal: true});
73 return false;
74 };
75 };
76
77
78 SaveWidget.prototype.status_save = function () {
79 this.element.find('span.ui-button-text').text('Save');
80 this.element.find('button#save_notebook').button('enable');
81 };
82
83
84 SaveWidget.prototype.status_saving = function () {
85 this.element.find('span.ui-button-text').text('Saving');
86 this.element.find('button#save_notebook').button('disable');
87 };
88
89
90 SaveWidget.prototype.status_loading = function () {
91 this.element.find('span.ui-button-text').text('Loading');
92 this.element.find('button#save_notebook').button('disable');
93 };
94
95
47 96 IPython.SaveWidget = SaveWidget;
48 97
49 98 return IPython;
50 99
51 100 }(IPython));
52 101
@@ -1,150 +1,152
1 1
2 2 //============================================================================
3 3 // TextCell
4 4 //============================================================================
5 5
6 6 var IPython = (function (IPython) {
7 7
8 8 var TextCell = function (notebook) {
9 9 IPython.Cell.apply(this, arguments);
10 10 this.placeholder = "Type <strong>HTML</strong> and LaTeX: $\\alpha^2$"
11 11 this.rendered = false;
12 12 };
13 13
14 14
15 15 TextCell.prototype = new IPython.Cell();
16 16
17 17
18 18 TextCell.prototype.create_element = function () {
19 19 var cell = $("<div>").addClass('cell text_cell border-box-sizing').
20 20 append(
21 21 $("<textarea>" + this.placeholder + "</textarea>").
22 22 addClass('text_cell_input').
23 23 attr('rows',1).
24 24 attr('cols',80).
25 25 autogrow()
26 26 ).append(
27 27 // The tabindex=-1 makes this div focusable.
28 28 $('<div></div>').addClass('text_cell_render').attr('tabindex','-1')
29 29 )
30 30 this.element = cell;
31 31 };
32 32
33 33
34 34 TextCell.prototype.bind_events = function () {
35 35 IPython.Cell.prototype.bind_events.apply(this);
36 36 var that = this;
37 37 this.element.keydown(function (event) {
38 38 if (event.which === 13) {
39 39 if (that.rendered) {
40 40 that.edit();
41 41 event.preventDefault();
42 42 };
43 43 };
44 44 });
45 45 };
46 46
47 47
48 48 TextCell.prototype.select = function () {
49 49 IPython.Cell.prototype.select.apply(this);
50 50 var output = this.element.find("div.text_cell_render");
51 51 output.trigger('focus');
52 52 };
53 53
54 54
55 55 TextCell.prototype.edit = function () {
56 56 if (this.rendered === true) {
57 57 var text_cell = this.element;
58 58 var input = text_cell.find("textarea.text_cell_input");
59 59 var output = text_cell.find("div.text_cell_render");
60 60 output.hide();
61 61 input.show().trigger('focus');
62 62 this.rendered = false;
63 63 };
64 64 };
65 65
66 66
67 67 TextCell.prototype.render = function () {
68 68 if (this.rendered === false) {
69 69 var text_cell = this.element;
70 70 var input = text_cell.find("textarea.text_cell_input");
71 71 var output = text_cell.find("div.text_cell_render");
72 72 var text = input.val();
73 73 if (text === "") {
74 74 text = this.placeholder;
75 75 input.val(text);
76 76 };
77 77 output.html(text)
78 78 input.html(text);
79 79 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
80 80 input.hide();
81 81 output.show();
82 82 this.rendered = true;
83 83 };
84 84 };
85 85
86 86
87 87 TextCell.prototype.config_mathjax = function () {
88 88 var text_cell = this.element;
89 89 var that = this;
90 90 text_cell.click(function () {
91 91 that.edit();
92 92 }).focusout(function () {
93 93 that.render();
94 94 });
95 95
96 96 text_cell.trigger("focusout");
97 97 };
98 98
99 99
100 100 TextCell.prototype.get_text = function() {
101 101 return this.element.find("textarea.text_cell_input").val();
102 102 };
103 103
104 104
105 105 TextCell.prototype.set_text = function(text) {
106 106 this.element.find("textarea.text_cell_input").val(text);
107 107 this.element.find("textarea.text_cell_input").html(text);
108 108 this.element.find("div.text_cell_render").html(text);
109 109 };
110 110
111 111
112 112 TextCell.prototype.at_top = function () {
113 113 if (this.rendered) {
114 114 return true;
115 115 } else {
116 116 return false;
117 117 }
118 118 };
119 119
120 120
121 121 TextCell.prototype.at_bottom = function () {
122 122 if (this.rendered) {
123 123 return true;
124 124 } else {
125 125 return false;
126 126 }
127 127 };
128 128
129 129
130 130 TextCell.prototype.fromJSON = function (data) {
131 131 if (data.cell_type === 'text') {
132 if (data.text !== undefined) {
132 133 this.set_text(data.text);
133 134 this.grow(this.element.find("textarea.text_cell_input"));
134 135 };
136 };
135 137 }
136 138
137 139
138 140 TextCell.prototype.toJSON = function () {
139 return {
140 cell_type : 'text',
141 text : this.get_text(),
142 };
141 var data = {}
142 data.cell_type = 'text';
143 data.text = this.get_text();
144 return data;
143 145 };
144 146
145 147 IPython.TextCell = TextCell;
146 148
147 149 return IPython;
148 150
149 151 }(IPython));
150 152
@@ -1,173 +1,186
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 9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
10 10 <!-- <link rel="stylesheet" href="static/jquery/css/themes/rocket/jquery-wijmo.css" type="text/css" /> -->
11 11 <!-- <link rel="stylesheet" href="static/jquery/css/themes/smoothness/jquery-ui-1.8.14.custom.css" type="text/css" />-->
12 12
13 13 <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" charset="utf-8"></script>
14 14 <!-- <script type='text/javascript' src='static/mathjax/MathJax.js?config=TeX-AMS_HTML' charset='utf-8'></script> -->
15 15 <script type="text/javascript">
16 16 if (typeof MathJax == 'undefined') {
17 17 console.log("Trying to load local copy of MathJax");
18 18 document.write(unescape("%3Cscript type='text/javascript' src='static/mathjax/MathJax.js%3Fconfig=TeX-AMS_HTML' charset='utf-8'%3E%3C/script%3E"));
19 19 }
20 20 </script>
21 21
22 22 <link rel="stylesheet" href="static/codemirror2/lib/codemirror.css">
23 23 <link rel="stylesheet" href="static/codemirror2/mode/python/python.css">
24 24
25 25 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
26 26 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
27 27
28 28 </head>
29 29
30 30 <body>
31 31
32 32 <div id="header">
33 33 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
34 34 <span id="save_widget">
35 35 <input type="text" id="notebook_name" size="20"></textarea>
36 <span id="notebook_id" style="display:none">{{notebook_id}}</span>
36 37 <button id="save_notebook">Save</button>
37 38 </span>
38 39 <span id="kernel_status">Idle</span>
39 40 </div>
40 41
41 42 <div id="notebook_app">
42 43
43 44 <div id="left_panel">
44 45
45 46 <div id="notebook_section">
46 47 <h3 class="section_header">Notebook</h3>
47 48 <div class="section_content">
48 49 <div class="section_row">
49 50 <span id="new_open" class="section_row_buttons">
50 51 <button id="new_notebook">New</button>
51 52 <button id="open_notebook">Open</button>
52 53 </span>
53 54 <span class="section_row_header">Actions</span>
54 55 </div>
56 <div class="section_row">
57 <span class="section_row_buttons">
58 <button id="download_notebook">Export</button>
59 </span>
60 <span>
61 <select id="download_format">
62 <option value="xml">xml</option>
63 <option value="json">json</option>
64 <option value="py">py</option>
65 </select>
66 </span>
67 </div>
55 68 </div>
56 69 </div>
57 70
58 71 <div id="cell_section">
59 72 <h3 class="section_header">Cell</h3>
60 73 <div class="section_content">
61 74 <div class="section_row">
62 75 <span class="section_row_buttons">
63 76 <button id="delete_cell">Delete</button>
64 77 </span>
65 78 <span class="section_row_header">Actions</span>
66 79 </div>
67 80 <div class="section_row">
68 81 <span id="insert" class="section_row_buttons">
69 82 <button id="insert_cell_above">Above</button>
70 83 <button id="insert_cell_below">Below</button>
71 84 </span>
72 85 <span class="button_label">Insert</span>
73 86 </div>
74 87 <div class="section_row">
75 88 <span id="move" class="section_row_buttons">
76 89 <button id="move_cell_up">Up</button>
77 90 <button id="move_cell_down">Down</button>
78 91 </span>
79 92 <span class="button_label">Move</span>
80 93 </div>
81 94 <div class="section_row">
82 95 <span id="cell_type" class="section_row_buttons">
83 96 <button id="to_code">Code</button>
84 97 <button id="to_text">Text</button>
85 98 </span>
86 99 <span class="button_label">Cell Type</span>
87 100 </div>
88 101 <div class="section_row">
89 102 <span id="toggle_output" class="section_row_buttons">
90 103 <button id="collapse_cell">Collapse</button>
91 104 <button id="expand_cell">Expand</button>
92 105 </span>
93 106 <span class="button_label">Output</span>
94 107 </div>
95 108 <div class="section_row">
96 109 <span id="run_cells" class="section_row_buttons">
97 110 <button id="run_selected_cell">Selected</button>
98 111 <button id="run_all_cells">All</button>
99 112 </span>
100 113 <span class="button_label">Run</span>
101 114 </div>
102 115 </div>
103 116 </div>
104 117
105 118 <div id="kernel_section">
106 119 <h3 class="section_header">Kernel</h3>
107 120 <div class="section_content">
108 121 <div class="section_row">
109 122 <span id="int_restart" class="section_row_buttons">
110 123 <button id="int_kernel">Interrupt</button>
111 124 <button id="restart_kernel">Restart</button>
112 125 </span>
113 126 <span class="section_row_header">Actions</span>
114 127 </div>
115 128 </div>
116 129 </div>
117 130
118 131 <div id="help_section">
119 132 <h3 class="section_header">Help</h3>
120 133 <div class="section_content">
121 134 <div class="section_row">
122 135 <span id="help_buttons0" class="section_row_buttons">
123 136 <button id="python_help"><a href="http://docs.python.org" target="_blank">Python</a></button>
124 137 <button id="ipython_help"><a href="http://ipython.org/documentation.html" target="_blank">IPython</a></button>
125 138 <button id="numpy_help"><a href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a></button>
126 139 </span>
127 140 <span class="section_row_header">Links</span>
128 141 </div>
129 142 <div class="section_row">
130 143 <span id="help_buttons1" class="section_row_buttons">
131 144 <button id="matplotlib_help"><a href="http://matplotlib.sourceforge.net/" target="_blank">MPL</a></button>
132 145 <button id="scipy_help"><a href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a></button>
133 146 <button id="sympy_help"><a href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a></button>
134 147 </span>
135 148 </div>
136 149 </div>
137 150 </div>
138 151
139 152 </div>
140 153 <div id="left_panel_splitter"></div>
141 154 <div id="notebook_panel">
142 155 <div id="notebook"></div>
143 156 <div id="pager_splitter"></div>
144 157 <div id="pager"></div>
145 158 </div>
146 159
147 160 </div>
148 161
149 162 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
150 163 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
151 164 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
152 165 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
153 166 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
154 167 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
155 168 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
156 169 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
157 170 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
158 171 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
159 172 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
160 173 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
161 174 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
162 175 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
163 176 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
164 177 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
165 178 <script src="static/js/notebook_main.js" type="text/javascript" charset="utf-8"></script>
166 179 <script src="static/codemirror2/lib/codemirror.js"></script>
167 180 <script src="static/codemirror2/mode/python/python.js"></script>
168 181
169 182 </body>
170 183
171 184 </html>
172 185
173 186
@@ -1,190 +1,195
1 1 import json
2 2 from xml.etree import ElementTree as ET
3 3 import re
4 4
5 5 from IPython.nbformat import v2
6 6 from IPython.nbformat import v1
7 7
8 from IPython.nbformat.v2 import (
9 NotebookNode,
10 new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet
11 )
12
8 13
9 14 current_nbformat = 2
10 15
11 16
12 17 class NBFormatError(Exception):
13 18 pass
14 19
15 20
16 21 def parse_json(s, **kwargs):
17 22 """Parse a string into a (nbformat, dict) tuple."""
18 23 d = json.loads(s, **kwargs)
19 24 nbformat = d.get('nbformat',1)
20 25 return nbformat, d
21 26
22 27
23 28 def parse_xml(s, **kwargs):
24 29 """Parse a string into a (nbformat, etree) tuple."""
25 30 root = ET.fromstring(s)
26 31 nbformat_e = root.find('nbformat')
27 32 if nbformat_e is not None:
28 33 nbformat = int(nbformat_e.text)
29 34 else:
30 35 raise NBFormatError('No nbformat version found')
31 36 return nbformat, root
32 37
33 38
34 39 def parse_py(s, **kwargs):
35 40 """Parse a string into a (nbformat, string) tuple."""
36 41 pattern = r'# <nbformat>(?P<nbformat>\d+)</nbformat>'
37 42 m = re.search(pattern,s)
38 43 if m is not None:
39 44 nbformat = int(m.group('nbformat'))
40 45 else:
41 46 raise NBFormatError('No nbformat version found')
42 47 return nbformat, s
43 48
44 49
45 50 def reads_json(s, **kwargs):
46 51 """Read a JSON notebook from a string and return the NotebookNode object."""
47 52 nbformat, d = parse_json(s, **kwargs)
48 53 if nbformat == 1:
49 54 nb = v1.to_notebook_json(d, **kwargs)
50 55 nb = v2.convert_to_this_nbformat(nb, orig_version=1)
51 56 elif nbformat == 2:
52 57 nb = v2.to_notebook_json(d, **kwargs)
53 58 else:
54 59 raise NBFormatError('Unsupported JSON nbformat version: %i' % nbformat)
55 60 return nb
56 61
57 62
58 63 def writes_json(nb, **kwargs):
59 64 return v2.writes_json(nb, **kwargs)
60 65
61 66
62 67 def reads_xml(s, **kwargs):
63 68 """Read an XML notebook from a string and return the NotebookNode object."""
64 69 nbformat, root = parse_xml(s, **kwargs)
65 70 if nbformat == 2:
66 71 nb = v2.to_notebook_xml(root, **kwargs)
67 72 else:
68 73 raise NBFormatError('Unsupported XML nbformat version: %i' % nbformat)
69 74 return nb
70 75
71 76
72 77 def writes_xml(nb, **kwargs):
73 78 return v2.writes_xml(nb, **kwargs)
74 79
75 80
76 81 def reads_py(s, **kwargs):
77 82 """Read a .py notebook from a string and return the NotebookNode object."""
78 83 nbformat, s = parse_py(s, **kwargs)
79 84 if nbformat == 2:
80 85 nb = v2.to_notebook_py(s, **kwargs)
81 86 else:
82 87 raise NBFormatError('Unsupported PY nbformat version: %i' % nbformat)
83 88 return nb
84 89
85 90
86 91 def writes_py(nb, **kwargs):
87 92 return v2.writes_py(nb, **kwargs)
88 93
89 94
90 95 # High level API
91 96
92 97
93 98 def reads(s, format, **kwargs):
94 99 """Read a notebook from a string and return the NotebookNode object.
95 100
96 101 This function properly handles notebooks of any version. The notebook
97 102 returned will always be in the current version's format.
98 103
99 104 Parameters
100 105 ----------
101 106 s : str
102 107 The raw string to read the notebook from.
103 108 format : ('xml','json','py')
104 109 The format that the string is in.
105 110
106 111 Returns
107 112 -------
108 113 nb : NotebookNode
109 114 The notebook that was read.
110 115 """
111 116 if format == 'xml':
112 117 return reads_xml(s, **kwargs)
113 118 elif format == 'json':
114 119 return reads_json(s, **kwargs)
115 120 elif format == 'py':
116 121 return reads_py(s, **kwargs)
117 122 else:
118 123 raise NBFormatError('Unsupported format: %s' % format)
119 124
120 125
121 126 def writes(nb, format, **kwargs):
122 127 """Write a notebook to a string in a given format in the current nbformat version.
123 128
124 129 This function always writes the notebook in the current nbformat version.
125 130
126 131 Parameters
127 132 ----------
128 133 nb : NotebookNode
129 134 The notebook to write.
130 135 format : ('xml','json','py')
131 136 The format to write the notebook in.
132 137
133 138 Returns
134 139 -------
135 140 s : str
136 141 The notebook string.
137 142 """
138 143 if format == 'xml':
139 144 return writes_xml(nb, **kwargs)
140 145 elif format == 'json':
141 146 return writes_json(nb, **kwargs)
142 147 elif format == 'py':
143 148 return writes_py(nb, **kwargs)
144 149 else:
145 150 raise NBFormatError('Unsupported format: %s' % format)
146 151
147 152
148 153 def read(fp, format, **kwargs):
149 154 """Read a notebook from a file and return the NotebookNode object.
150 155
151 156 This function properly handles notebooks of any version. The notebook
152 157 returned will always be in the current version's format.
153 158
154 159 Parameters
155 160 ----------
156 161 fp : file
157 162 Any file-like object with a read method.
158 163 format : ('xml','json','py')
159 164 The format that the string is in.
160 165
161 166 Returns
162 167 -------
163 168 nb : NotebookNode
164 169 The notebook that was read.
165 170 """
166 171 return reads(fp.read(), format, **kwargs)
167 172
168 173
169 174 def write(nb, fp, format, **kwargs):
170 175 """Write a notebook to a file in a given format in the current nbformat version.
171 176
172 177 This function always writes the notebook in the current nbformat version.
173 178
174 179 Parameters
175 180 ----------
176 181 nb : NotebookNode
177 182 The notebook to write.
178 183 fp : file
179 184 Any file-like object with a write method.
180 185 format : ('xml','json','py')
181 186 The format to write the notebook in.
182 187
183 188 Returns
184 189 -------
185 190 s : str
186 191 The notebook string.
187 192 """
188 193 return fp.write(writes(nb, format, **kwargs))
189 194
190 195
@@ -1,55 +1,56
1 1 """Read and write notebooks as regular .py files."""
2 2
3 3 from .rwbase import NotebookReader, NotebookWriter
4 4 from .nbbase import new_code_cell, new_worksheet, new_notebook
5 5
6 6
7 7 class PyReader(NotebookReader):
8 8
9 9 def reads(self, s, **kwargs):
10 10 return self.to_notebook(s,**kwargs)
11 11
12 12 def to_notebook(self, s, **kwargs):
13 13 lines = s.splitlines()
14 14 cells = []
15 15 cell_lines = []
16 16 for line in lines:
17 17 if line.startswith(u'# <codecell>'):
18 18 cell_lines = []
19 19 if line.startswith(u'# </codecell>'):
20 20 code = u'\n'.join(cell_lines)
21 21 code = code.strip(u'\n')
22 22 if code:
23 23 cells.append(new_code_cell(input=code))
24 24 else:
25 25 cell_lines.append(line)
26 26 ws = new_worksheet(cells=cells)
27 27 nb = new_notebook(worksheets=[ws])
28 28 return nb
29 29
30 30
31 31 class PyWriter(NotebookWriter):
32 32
33 33 def writes(self, nb, **kwargs):
34 34 lines = []
35 35 lines.extend(['# <nbformat>2</nbformat>',''])
36 36 for ws in nb.worksheets:
37 37 for cell in ws.cells:
38 38 if cell.cell_type == 'code':
39 input = cell.input
39 input = cell.get('input')
40 if input is not None:
40 41 lines.extend([u'# <codecell>',u''])
41 42 lines.extend(input.splitlines())
42 43 lines.extend([u'',u'# </codecell>'])
43 44 lines.append('')
44 45 return unicode('\n'.join(lines))
45 46
46 47
47 48 _reader = PyReader()
48 49 _writer = PyWriter()
49 50
50 51 reads = _reader.reads
51 52 read = _reader.read
52 53 to_notebook = _reader.to_notebook
53 54 write = _writer.write
54 55 writes = _writer.writes
55 56
General Comments 0
You need to be logged in to leave comments. Login now