##// END OF EJS Templates
Merge pull request #3743 from minrk/noro...
Matthias Bussonnier -
r11691:c2464fa2 merge
parent child Browse files
Show More
@@ -1,450 +1,415 b''
1 """Base Tornado handlers for the notebook.
1 """Base Tornado handlers for the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19
19
20 import datetime
20 import datetime
21 import email.utils
21 import email.utils
22 import hashlib
22 import hashlib
23 import logging
23 import logging
24 import mimetypes
24 import mimetypes
25 import os
25 import os
26 import stat
26 import stat
27 import threading
27 import threading
28
28
29 from tornado import web
29 from tornado import web
30 from tornado import websocket
30 from tornado import websocket
31
31
32 try:
32 try:
33 from tornado.log import app_log
33 from tornado.log import app_log
34 except ImportError:
34 except ImportError:
35 app_log = logging.getLogger()
35 app_log = logging.getLogger()
36
36
37 from IPython.config import Application
37 from IPython.config import Application
38 from IPython.external.decorator import decorator
38 from IPython.external.decorator import decorator
39 from IPython.utils.path import filefind
39 from IPython.utils.path import filefind
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
42 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 # Google Chrome, as of release 16, changed its websocket protocol number. The
45 # Google Chrome, as of release 16, changed its websocket protocol number. The
46 # parts tornado cares about haven't really changed, so it's OK to continue
46 # parts tornado cares about haven't really changed, so it's OK to continue
47 # accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
47 # accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
48 # version as of Oct 30/2011) the version check fails, see the issue report:
48 # version as of Oct 30/2011) the version check fails, see the issue report:
49
49
50 # https://github.com/facebook/tornado/issues/385
50 # https://github.com/facebook/tornado/issues/385
51
51
52 # This issue has been fixed in Tornado post 2.1.1:
52 # This issue has been fixed in Tornado post 2.1.1:
53
53
54 # https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
54 # https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
55
55
56 # Here we manually apply the same patch as above so that users of IPython can
56 # Here we manually apply the same patch as above so that users of IPython can
57 # continue to work with an officially released Tornado. We make the
57 # continue to work with an officially released Tornado. We make the
58 # monkeypatch version check as narrow as possible to limit its effects; once
58 # monkeypatch version check as narrow as possible to limit its effects; once
59 # Tornado 2.1.1 is no longer found in the wild we'll delete this code.
59 # Tornado 2.1.1 is no longer found in the wild we'll delete this code.
60
60
61 import tornado
61 import tornado
62
62
63 if tornado.version_info <= (2,1,1):
63 if tornado.version_info <= (2,1,1):
64
64
65 def _execute(self, transforms, *args, **kwargs):
65 def _execute(self, transforms, *args, **kwargs):
66 from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
66 from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
67
67
68 self.open_args = args
68 self.open_args = args
69 self.open_kwargs = kwargs
69 self.open_kwargs = kwargs
70
70
71 # The difference between version 8 and 13 is that in 8 the
71 # The difference between version 8 and 13 is that in 8 the
72 # client sends a "Sec-Websocket-Origin" header and in 13 it's
72 # client sends a "Sec-Websocket-Origin" header and in 13 it's
73 # simply "Origin".
73 # simply "Origin".
74 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
74 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
75 self.ws_connection = WebSocketProtocol8(self)
75 self.ws_connection = WebSocketProtocol8(self)
76 self.ws_connection.accept_connection()
76 self.ws_connection.accept_connection()
77
77
78 elif self.request.headers.get("Sec-WebSocket-Version"):
78 elif self.request.headers.get("Sec-WebSocket-Version"):
79 self.stream.write(tornado.escape.utf8(
79 self.stream.write(tornado.escape.utf8(
80 "HTTP/1.1 426 Upgrade Required\r\n"
80 "HTTP/1.1 426 Upgrade Required\r\n"
81 "Sec-WebSocket-Version: 8\r\n\r\n"))
81 "Sec-WebSocket-Version: 8\r\n\r\n"))
82 self.stream.close()
82 self.stream.close()
83
83
84 else:
84 else:
85 self.ws_connection = WebSocketProtocol76(self)
85 self.ws_connection = WebSocketProtocol76(self)
86 self.ws_connection.accept_connection()
86 self.ws_connection.accept_connection()
87
87
88 websocket.WebSocketHandler._execute = _execute
88 websocket.WebSocketHandler._execute = _execute
89 del _execute
89 del _execute
90
90
91 #-----------------------------------------------------------------------------
92 # Decorator for disabling read-only handlers
93 #-----------------------------------------------------------------------------
94
95 @decorator
96 def not_if_readonly(f, self, *args, **kwargs):
97 if self.settings.get('read_only', False):
98 raise web.HTTPError(403, "Notebook server is read-only")
99 else:
100 return f(self, *args, **kwargs)
101
102 @decorator
103 def authenticate_unless_readonly(f, self, *args, **kwargs):
104 """authenticate this page *unless* readonly view is active.
105
106 In read-only mode, the notebook list and print view should
107 be accessible without authentication.
108 """
109
110 @web.authenticated
111 def auth_f(self, *args, **kwargs):
112 return f(self, *args, **kwargs)
113
114 if self.settings.get('read_only', False):
115 return f(self, *args, **kwargs)
116 else:
117 return auth_f(self, *args, **kwargs)
118
91
119 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
120 # Top-level handlers
93 # Top-level handlers
121 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
122
95
123 class RequestHandler(web.RequestHandler):
96 class RequestHandler(web.RequestHandler):
124 """RequestHandler with default variable setting."""
97 """RequestHandler with default variable setting."""
125
98
126 def render(*args, **kwargs):
99 def render(*args, **kwargs):
127 kwargs.setdefault('message', '')
100 kwargs.setdefault('message', '')
128 return web.RequestHandler.render(*args, **kwargs)
101 return web.RequestHandler.render(*args, **kwargs)
129
102
130 class AuthenticatedHandler(RequestHandler):
103 class AuthenticatedHandler(RequestHandler):
131 """A RequestHandler with an authenticated user."""
104 """A RequestHandler with an authenticated user."""
132
105
133 def clear_login_cookie(self):
106 def clear_login_cookie(self):
134 self.clear_cookie(self.cookie_name)
107 self.clear_cookie(self.cookie_name)
135
108
136 def get_current_user(self):
109 def get_current_user(self):
137 user_id = self.get_secure_cookie(self.cookie_name)
110 user_id = self.get_secure_cookie(self.cookie_name)
138 # For now the user_id should not return empty, but it could eventually
111 # For now the user_id should not return empty, but it could eventually
139 if user_id == '':
112 if user_id == '':
140 user_id = 'anonymous'
113 user_id = 'anonymous'
141 if user_id is None:
114 if user_id is None:
142 # prevent extra Invalid cookie sig warnings:
115 # prevent extra Invalid cookie sig warnings:
143 self.clear_login_cookie()
116 self.clear_login_cookie()
144 if not self.read_only and not self.login_available:
117 if not self.login_available:
145 user_id = 'anonymous'
118 user_id = 'anonymous'
146 return user_id
119 return user_id
147
120
148 @property
121 @property
149 def cookie_name(self):
122 def cookie_name(self):
150 default_cookie_name = 'username-{host}'.format(
123 default_cookie_name = 'username-{host}'.format(
151 host=self.request.host,
124 host=self.request.host,
152 ).replace(':', '-')
125 ).replace(':', '-')
153 return self.settings.get('cookie_name', default_cookie_name)
126 return self.settings.get('cookie_name', default_cookie_name)
154
127
155 @property
128 @property
156 def password(self):
129 def password(self):
157 """our password"""
130 """our password"""
158 return self.settings.get('password', '')
131 return self.settings.get('password', '')
159
132
160 @property
133 @property
161 def logged_in(self):
134 def logged_in(self):
162 """Is a user currently logged in?
135 """Is a user currently logged in?
163
136
164 """
137 """
165 user = self.get_current_user()
138 user = self.get_current_user()
166 return (user and not user == 'anonymous')
139 return (user and not user == 'anonymous')
167
140
168 @property
141 @property
169 def login_available(self):
142 def login_available(self):
170 """May a user proceed to log in?
143 """May a user proceed to log in?
171
144
172 This returns True if login capability is available, irrespective of
145 This returns True if login capability is available, irrespective of
173 whether the user is already logged in or not.
146 whether the user is already logged in or not.
174
147
175 """
148 """
176 return bool(self.settings.get('password', ''))
149 return bool(self.settings.get('password', ''))
177
150
178 @property
179 def read_only(self):
180 """Is the notebook read-only?
181
182 """
183 return self.settings.get('read_only', False)
184
185
151
186 class IPythonHandler(AuthenticatedHandler):
152 class IPythonHandler(AuthenticatedHandler):
187 """IPython-specific extensions to authenticated handling
153 """IPython-specific extensions to authenticated handling
188
154
189 Mostly property shortcuts to IPython-specific settings.
155 Mostly property shortcuts to IPython-specific settings.
190 """
156 """
191
157
192 @property
158 @property
193 def config(self):
159 def config(self):
194 return self.settings.get('config', None)
160 return self.settings.get('config', None)
195
161
196 @property
162 @property
197 def log(self):
163 def log(self):
198 """use the IPython log by default, falling back on tornado's logger"""
164 """use the IPython log by default, falling back on tornado's logger"""
199 if Application.initialized():
165 if Application.initialized():
200 return Application.instance().log
166 return Application.instance().log
201 else:
167 else:
202 return app_log
168 return app_log
203
169
204 @property
170 @property
205 def use_less(self):
171 def use_less(self):
206 """Use less instead of css in templates"""
172 """Use less instead of css in templates"""
207 return self.settings.get('use_less', False)
173 return self.settings.get('use_less', False)
208
174
209 #---------------------------------------------------------------
175 #---------------------------------------------------------------
210 # URLs
176 # URLs
211 #---------------------------------------------------------------
177 #---------------------------------------------------------------
212
178
213 @property
179 @property
214 def ws_url(self):
180 def ws_url(self):
215 """websocket url matching the current request
181 """websocket url matching the current request
216
182
217 By default, this is just `''`, indicating that it should match
183 By default, this is just `''`, indicating that it should match
218 the same host, protocol, port, etc.
184 the same host, protocol, port, etc.
219 """
185 """
220 return self.settings.get('websocket_url', '')
186 return self.settings.get('websocket_url', '')
221
187
222 @property
188 @property
223 def mathjax_url(self):
189 def mathjax_url(self):
224 return self.settings.get('mathjax_url', '')
190 return self.settings.get('mathjax_url', '')
225
191
226 @property
192 @property
227 def base_project_url(self):
193 def base_project_url(self):
228 return self.settings.get('base_project_url', '/')
194 return self.settings.get('base_project_url', '/')
229
195
230 @property
196 @property
231 def base_kernel_url(self):
197 def base_kernel_url(self):
232 return self.settings.get('base_kernel_url', '/')
198 return self.settings.get('base_kernel_url', '/')
233
199
234 #---------------------------------------------------------------
200 #---------------------------------------------------------------
235 # Manager objects
201 # Manager objects
236 #---------------------------------------------------------------
202 #---------------------------------------------------------------
237
203
238 @property
204 @property
239 def kernel_manager(self):
205 def kernel_manager(self):
240 return self.settings['kernel_manager']
206 return self.settings['kernel_manager']
241
207
242 @property
208 @property
243 def notebook_manager(self):
209 def notebook_manager(self):
244 return self.settings['notebook_manager']
210 return self.settings['notebook_manager']
245
211
246 @property
212 @property
247 def cluster_manager(self):
213 def cluster_manager(self):
248 return self.settings['cluster_manager']
214 return self.settings['cluster_manager']
249
215
250 @property
216 @property
251 def project(self):
217 def project(self):
252 return self.notebook_manager.notebook_dir
218 return self.notebook_manager.notebook_dir
253
219
254 #---------------------------------------------------------------
220 #---------------------------------------------------------------
255 # template rendering
221 # template rendering
256 #---------------------------------------------------------------
222 #---------------------------------------------------------------
257
223
258 def get_template(self, name):
224 def get_template(self, name):
259 """Return the jinja template object for a given name"""
225 """Return the jinja template object for a given name"""
260 return self.settings['jinja2_env'].get_template(name)
226 return self.settings['jinja2_env'].get_template(name)
261
227
262 def render_template(self, name, **ns):
228 def render_template(self, name, **ns):
263 ns.update(self.template_namespace)
229 ns.update(self.template_namespace)
264 template = self.get_template(name)
230 template = self.get_template(name)
265 return template.render(**ns)
231 return template.render(**ns)
266
232
267 @property
233 @property
268 def template_namespace(self):
234 def template_namespace(self):
269 return dict(
235 return dict(
270 base_project_url=self.base_project_url,
236 base_project_url=self.base_project_url,
271 base_kernel_url=self.base_kernel_url,
237 base_kernel_url=self.base_kernel_url,
272 read_only=self.read_only,
273 logged_in=self.logged_in,
238 logged_in=self.logged_in,
274 login_available=self.login_available,
239 login_available=self.login_available,
275 use_less=self.use_less,
240 use_less=self.use_less,
276 )
241 )
277
242
278 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
243 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
279 """static files should only be accessible when logged in"""
244 """static files should only be accessible when logged in"""
280
245
281 @authenticate_unless_readonly
246 @web.authenticated
282 def get(self, path):
247 def get(self, path):
283 return web.StaticFileHandler.get(self, path)
248 return web.StaticFileHandler.get(self, path)
284
249
285
250
286 #-----------------------------------------------------------------------------
251 #-----------------------------------------------------------------------------
287 # File handler
252 # File handler
288 #-----------------------------------------------------------------------------
253 #-----------------------------------------------------------------------------
289
254
290 # to minimize subclass changes:
255 # to minimize subclass changes:
291 HTTPError = web.HTTPError
256 HTTPError = web.HTTPError
292
257
293 class FileFindHandler(web.StaticFileHandler):
258 class FileFindHandler(web.StaticFileHandler):
294 """subclass of StaticFileHandler for serving files from a search path"""
259 """subclass of StaticFileHandler for serving files from a search path"""
295
260
296 _static_paths = {}
261 _static_paths = {}
297 # _lock is needed for tornado < 2.2.0 compat
262 # _lock is needed for tornado < 2.2.0 compat
298 _lock = threading.Lock() # protects _static_hashes
263 _lock = threading.Lock() # protects _static_hashes
299
264
300 def initialize(self, path, default_filename=None):
265 def initialize(self, path, default_filename=None):
301 if isinstance(path, basestring):
266 if isinstance(path, basestring):
302 path = [path]
267 path = [path]
303 self.roots = tuple(
268 self.roots = tuple(
304 os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in path
269 os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in path
305 )
270 )
306 self.default_filename = default_filename
271 self.default_filename = default_filename
307
272
308 @classmethod
273 @classmethod
309 def locate_file(cls, path, roots):
274 def locate_file(cls, path, roots):
310 """locate a file to serve on our static file search path"""
275 """locate a file to serve on our static file search path"""
311 with cls._lock:
276 with cls._lock:
312 if path in cls._static_paths:
277 if path in cls._static_paths:
313 return cls._static_paths[path]
278 return cls._static_paths[path]
314 try:
279 try:
315 abspath = os.path.abspath(filefind(path, roots))
280 abspath = os.path.abspath(filefind(path, roots))
316 except IOError:
281 except IOError:
317 # empty string should always give exists=False
282 # empty string should always give exists=False
318 return ''
283 return ''
319
284
320 # os.path.abspath strips a trailing /
285 # os.path.abspath strips a trailing /
321 # it needs to be temporarily added back for requests to root/
286 # it needs to be temporarily added back for requests to root/
322 if not (abspath + os.path.sep).startswith(roots):
287 if not (abspath + os.path.sep).startswith(roots):
323 raise HTTPError(403, "%s is not in root static directory", path)
288 raise HTTPError(403, "%s is not in root static directory", path)
324
289
325 cls._static_paths[path] = abspath
290 cls._static_paths[path] = abspath
326 return abspath
291 return abspath
327
292
328 def get(self, path, include_body=True):
293 def get(self, path, include_body=True):
329 path = self.parse_url_path(path)
294 path = self.parse_url_path(path)
330
295
331 # begin subclass override
296 # begin subclass override
332 abspath = self.locate_file(path, self.roots)
297 abspath = self.locate_file(path, self.roots)
333 # end subclass override
298 # end subclass override
334
299
335 if os.path.isdir(abspath) and self.default_filename is not None:
300 if os.path.isdir(abspath) and self.default_filename is not None:
336 # need to look at the request.path here for when path is empty
301 # need to look at the request.path here for when path is empty
337 # but there is some prefix to the path that was already
302 # but there is some prefix to the path that was already
338 # trimmed by the routing
303 # trimmed by the routing
339 if not self.request.path.endswith("/"):
304 if not self.request.path.endswith("/"):
340 self.redirect(self.request.path + "/")
305 self.redirect(self.request.path + "/")
341 return
306 return
342 abspath = os.path.join(abspath, self.default_filename)
307 abspath = os.path.join(abspath, self.default_filename)
343 if not os.path.exists(abspath):
308 if not os.path.exists(abspath):
344 raise HTTPError(404)
309 raise HTTPError(404)
345 if not os.path.isfile(abspath):
310 if not os.path.isfile(abspath):
346 raise HTTPError(403, "%s is not a file", path)
311 raise HTTPError(403, "%s is not a file", path)
347
312
348 stat_result = os.stat(abspath)
313 stat_result = os.stat(abspath)
349 modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
314 modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
350
315
351 self.set_header("Last-Modified", modified)
316 self.set_header("Last-Modified", modified)
352
317
353 mime_type, encoding = mimetypes.guess_type(abspath)
318 mime_type, encoding = mimetypes.guess_type(abspath)
354 if mime_type:
319 if mime_type:
355 self.set_header("Content-Type", mime_type)
320 self.set_header("Content-Type", mime_type)
356
321
357 cache_time = self.get_cache_time(path, modified, mime_type)
322 cache_time = self.get_cache_time(path, modified, mime_type)
358
323
359 if cache_time > 0:
324 if cache_time > 0:
360 self.set_header("Expires", datetime.datetime.utcnow() + \
325 self.set_header("Expires", datetime.datetime.utcnow() + \
361 datetime.timedelta(seconds=cache_time))
326 datetime.timedelta(seconds=cache_time))
362 self.set_header("Cache-Control", "max-age=" + str(cache_time))
327 self.set_header("Cache-Control", "max-age=" + str(cache_time))
363 else:
328 else:
364 self.set_header("Cache-Control", "public")
329 self.set_header("Cache-Control", "public")
365
330
366 self.set_extra_headers(path)
331 self.set_extra_headers(path)
367
332
368 # Check the If-Modified-Since, and don't send the result if the
333 # Check the If-Modified-Since, and don't send the result if the
369 # content has not been modified
334 # content has not been modified
370 ims_value = self.request.headers.get("If-Modified-Since")
335 ims_value = self.request.headers.get("If-Modified-Since")
371 if ims_value is not None:
336 if ims_value is not None:
372 date_tuple = email.utils.parsedate(ims_value)
337 date_tuple = email.utils.parsedate(ims_value)
373 if_since = datetime.datetime(*date_tuple[:6])
338 if_since = datetime.datetime(*date_tuple[:6])
374 if if_since >= modified:
339 if if_since >= modified:
375 self.set_status(304)
340 self.set_status(304)
376 return
341 return
377
342
378 with open(abspath, "rb") as file:
343 with open(abspath, "rb") as file:
379 data = file.read()
344 data = file.read()
380 hasher = hashlib.sha1()
345 hasher = hashlib.sha1()
381 hasher.update(data)
346 hasher.update(data)
382 self.set_header("Etag", '"%s"' % hasher.hexdigest())
347 self.set_header("Etag", '"%s"' % hasher.hexdigest())
383 if include_body:
348 if include_body:
384 self.write(data)
349 self.write(data)
385 else:
350 else:
386 assert self.request.method == "HEAD"
351 assert self.request.method == "HEAD"
387 self.set_header("Content-Length", len(data))
352 self.set_header("Content-Length", len(data))
388
353
389 @classmethod
354 @classmethod
390 def get_version(cls, settings, path):
355 def get_version(cls, settings, path):
391 """Generate the version string to be used in static URLs.
356 """Generate the version string to be used in static URLs.
392
357
393 This method may be overridden in subclasses (but note that it
358 This method may be overridden in subclasses (but note that it
394 is a class method rather than a static method). The default
359 is a class method rather than a static method). The default
395 implementation uses a hash of the file's contents.
360 implementation uses a hash of the file's contents.
396
361
397 ``settings`` is the `Application.settings` dictionary and ``path``
362 ``settings`` is the `Application.settings` dictionary and ``path``
398 is the relative location of the requested asset on the filesystem.
363 is the relative location of the requested asset on the filesystem.
399 The returned value should be a string, or ``None`` if no version
364 The returned value should be a string, or ``None`` if no version
400 could be determined.
365 could be determined.
401 """
366 """
402 # begin subclass override:
367 # begin subclass override:
403 static_paths = settings['static_path']
368 static_paths = settings['static_path']
404 if isinstance(static_paths, basestring):
369 if isinstance(static_paths, basestring):
405 static_paths = [static_paths]
370 static_paths = [static_paths]
406 roots = tuple(
371 roots = tuple(
407 os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
372 os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
408 )
373 )
409
374
410 try:
375 try:
411 abs_path = filefind(path, roots)
376 abs_path = filefind(path, roots)
412 except IOError:
377 except IOError:
413 app_log.error("Could not find static file %r", path)
378 app_log.error("Could not find static file %r", path)
414 return None
379 return None
415
380
416 # end subclass override
381 # end subclass override
417
382
418 with cls._lock:
383 with cls._lock:
419 hashes = cls._static_hashes
384 hashes = cls._static_hashes
420 if abs_path not in hashes:
385 if abs_path not in hashes:
421 try:
386 try:
422 f = open(abs_path, "rb")
387 f = open(abs_path, "rb")
423 hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
388 hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
424 f.close()
389 f.close()
425 except Exception:
390 except Exception:
426 app_log.error("Could not open static file %r", path)
391 app_log.error("Could not open static file %r", path)
427 hashes[abs_path] = None
392 hashes[abs_path] = None
428 hsh = hashes.get(abs_path)
393 hsh = hashes.get(abs_path)
429 if hsh:
394 if hsh:
430 return hsh[:5]
395 return hsh[:5]
431 return None
396 return None
432
397
433
398
434 def parse_url_path(self, url_path):
399 def parse_url_path(self, url_path):
435 """Converts a static URL path into a filesystem path.
400 """Converts a static URL path into a filesystem path.
436
401
437 ``url_path`` is the path component of the URL with
402 ``url_path`` is the path component of the URL with
438 ``static_url_prefix`` removed. The return value should be
403 ``static_url_prefix`` removed. The return value should be
439 filesystem path relative to ``static_path``.
404 filesystem path relative to ``static_path``.
440 """
405 """
441 if os.path.sep != "/":
406 if os.path.sep != "/":
442 url_path = url_path.replace("/", os.path.sep)
407 url_path = url_path.replace("/", os.path.sep)
443 return url_path
408 return url_path
444
409
445 #-----------------------------------------------------------------------------
410 #-----------------------------------------------------------------------------
446 # URL to handler mappings
411 # URL to handler mappings
447 #-----------------------------------------------------------------------------
412 #-----------------------------------------------------------------------------
448
413
449
414
450 default_handlers = []
415 default_handlers = []
@@ -1,91 +1,91 b''
1 """Tornado handlers for the live notebook view.
1 """Tornado handlers for the live notebook view.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 from tornado import web
20 from tornado import web
21 HTTPError = web.HTTPError
21 HTTPError = web.HTTPError
22
22
23 from ..base.handlers import IPythonHandler, authenticate_unless_readonly
23 from ..base.handlers import IPythonHandler
24 from ..utils import url_path_join
24 from ..utils import url_path_join
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Handlers
27 # Handlers
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30
30
31 class NewHandler(IPythonHandler):
31 class NewHandler(IPythonHandler):
32
32
33 @web.authenticated
33 @web.authenticated
34 def get(self):
34 def get(self):
35 notebook_id = self.notebook_manager.new_notebook()
35 notebook_id = self.notebook_manager.new_notebook()
36 self.redirect(url_path_join(self.base_project_url, notebook_id))
36 self.redirect(url_path_join(self.base_project_url, notebook_id))
37
37
38
38
39 class NamedNotebookHandler(IPythonHandler):
39 class NamedNotebookHandler(IPythonHandler):
40
40
41 @authenticate_unless_readonly
41 @web.authenticated
42 def get(self, notebook_id):
42 def get(self, notebook_id):
43 nbm = self.notebook_manager
43 nbm = self.notebook_manager
44 if not nbm.notebook_exists(notebook_id):
44 if not nbm.notebook_exists(notebook_id):
45 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
45 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
46 self.write(self.render_template('notebook.html',
46 self.write(self.render_template('notebook.html',
47 project=self.project,
47 project=self.project,
48 notebook_id=notebook_id,
48 notebook_id=notebook_id,
49 kill_kernel=False,
49 kill_kernel=False,
50 mathjax_url=self.mathjax_url,
50 mathjax_url=self.mathjax_url,
51 )
51 )
52 )
52 )
53
53
54
54
55 class NotebookRedirectHandler(IPythonHandler):
55 class NotebookRedirectHandler(IPythonHandler):
56
56
57 @authenticate_unless_readonly
57 @web.authenticated
58 def get(self, notebook_name):
58 def get(self, notebook_name):
59 # strip trailing .ipynb:
59 # strip trailing .ipynb:
60 notebook_name = os.path.splitext(notebook_name)[0]
60 notebook_name = os.path.splitext(notebook_name)[0]
61 notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
61 notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
62 if notebook_id:
62 if notebook_id:
63 url = url_path_join(self.settings.get('base_project_url', '/'), notebook_id)
63 url = url_path_join(self.settings.get('base_project_url', '/'), notebook_id)
64 return self.redirect(url)
64 return self.redirect(url)
65 else:
65 else:
66 raise HTTPError(404)
66 raise HTTPError(404)
67
67
68
68
69 class NotebookCopyHandler(IPythonHandler):
69 class NotebookCopyHandler(IPythonHandler):
70
70
71 @web.authenticated
71 @web.authenticated
72 def get(self, notebook_id):
72 def get(self, notebook_id):
73 notebook_id = self.notebook_manager.copy_notebook(notebook_id)
73 notebook_id = self.notebook_manager.copy_notebook(notebook_id)
74 self.redirect(url_path_join(self.base_project_url, notebook_id))
74 self.redirect(url_path_join(self.base_project_url, notebook_id))
75
75
76
76
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78 # URL to handler mappings
78 # URL to handler mappings
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80
80
81
81
82 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
82 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
83 _notebook_name_regex = r"(?P<notebook_name>.+\.ipynb)"
83 _notebook_name_regex = r"(?P<notebook_name>.+\.ipynb)"
84
84
85 default_handlers = [
85 default_handlers = [
86 (r"/new", NewHandler),
86 (r"/new", NewHandler),
87 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
87 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
88 (r"/%s" % _notebook_name_regex, NotebookRedirectHandler),
88 (r"/%s" % _notebook_name_regex, NotebookRedirectHandler),
89 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
89 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
90
90
91 ]
91 ]
@@ -1,742 +1,725 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import random
23 import random
24 import select
24 import select
25 import signal
25 import signal
26 import socket
26 import socket
27 import sys
27 import sys
28 import threading
28 import threading
29 import time
29 import time
30 import webbrowser
30 import webbrowser
31
31
32
32
33 # Third party
33 # Third party
34 # check for pyzmq 2.1.11
34 # check for pyzmq 2.1.11
35 from IPython.utils.zmqrelated import check_for_zmq
35 from IPython.utils.zmqrelated import check_for_zmq
36 check_for_zmq('2.1.11', 'IPython.html')
36 check_for_zmq('2.1.11', 'IPython.html')
37
37
38 from jinja2 import Environment, FileSystemLoader
38 from jinja2 import Environment, FileSystemLoader
39
39
40 # Install the pyzmq ioloop. This has to be done before anything else from
40 # Install the pyzmq ioloop. This has to be done before anything else from
41 # tornado is imported.
41 # tornado is imported.
42 from zmq.eventloop import ioloop
42 from zmq.eventloop import ioloop
43 ioloop.install()
43 ioloop.install()
44
44
45 # check for tornado 2.1.0
45 # check for tornado 2.1.0
46 msg = "The IPython Notebook requires tornado >= 2.1.0"
46 msg = "The IPython Notebook requires tornado >= 2.1.0"
47 try:
47 try:
48 import tornado
48 import tornado
49 except ImportError:
49 except ImportError:
50 raise ImportError(msg)
50 raise ImportError(msg)
51 try:
51 try:
52 version_info = tornado.version_info
52 version_info = tornado.version_info
53 except AttributeError:
53 except AttributeError:
54 raise ImportError(msg + ", but you have < 1.1.0")
54 raise ImportError(msg + ", but you have < 1.1.0")
55 if version_info < (2,1,0):
55 if version_info < (2,1,0):
56 raise ImportError(msg + ", but you have %s" % tornado.version)
56 raise ImportError(msg + ", but you have %s" % tornado.version)
57
57
58 from tornado import httpserver
58 from tornado import httpserver
59 from tornado import web
59 from tornado import web
60
60
61 # Our own libraries
61 # Our own libraries
62 from IPython.html import DEFAULT_STATIC_FILES_PATH
62 from IPython.html import DEFAULT_STATIC_FILES_PATH
63
63
64 from .services.kernels.kernelmanager import MappingKernelManager
64 from .services.kernels.kernelmanager import MappingKernelManager
65 from .services.notebooks.nbmanager import NotebookManager
65 from .services.notebooks.nbmanager import NotebookManager
66 from .services.notebooks.filenbmanager import FileNotebookManager
66 from .services.notebooks.filenbmanager import FileNotebookManager
67 from .services.clusters.clustermanager import ClusterManager
67 from .services.clusters.clustermanager import ClusterManager
68
68
69 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
69 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
70
70
71 from IPython.config.application import catch_config_error, boolean_flag
71 from IPython.config.application import catch_config_error, boolean_flag
72 from IPython.core.application import BaseIPythonApplication
72 from IPython.core.application import BaseIPythonApplication
73 from IPython.consoleapp import IPythonConsoleApp
73 from IPython.consoleapp import IPythonConsoleApp
74 from IPython.kernel import swallow_argv
74 from IPython.kernel import swallow_argv
75 from IPython.kernel.zmq.session import default_secure
75 from IPython.kernel.zmq.session import default_secure
76 from IPython.kernel.zmq.kernelapp import (
76 from IPython.kernel.zmq.kernelapp import (
77 kernel_flags,
77 kernel_flags,
78 kernel_aliases,
78 kernel_aliases,
79 )
79 )
80 from IPython.utils.importstring import import_item
80 from IPython.utils.importstring import import_item
81 from IPython.utils.localinterfaces import LOCALHOST
81 from IPython.utils.localinterfaces import LOCALHOST
82 from IPython.utils import submodule
82 from IPython.utils import submodule
83 from IPython.utils.traitlets import (
83 from IPython.utils.traitlets import (
84 Dict, Unicode, Integer, List, Bool, Bytes,
84 Dict, Unicode, Integer, List, Bool, Bytes,
85 DottedObjectName
85 DottedObjectName
86 )
86 )
87 from IPython.utils import py3compat
87 from IPython.utils import py3compat
88 from IPython.utils.path import filefind
88 from IPython.utils.path import filefind
89
89
90 from .utils import url_path_join
90 from .utils import url_path_join
91
91
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93 # Module globals
93 # Module globals
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95
95
96 _examples = """
96 _examples = """
97 ipython notebook # start the notebook
97 ipython notebook # start the notebook
98 ipython notebook --profile=sympy # use the sympy profile
98 ipython notebook --profile=sympy # use the sympy profile
99 ipython notebook --pylab=inline # pylab in inline plotting mode
99 ipython notebook --pylab=inline # pylab in inline plotting mode
100 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
100 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
101 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
101 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
102 """
102 """
103
103
104 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
105 # Helper functions
105 # Helper functions
106 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
107
107
108 def random_ports(port, n):
108 def random_ports(port, n):
109 """Generate a list of n random ports near the given port.
109 """Generate a list of n random ports near the given port.
110
110
111 The first 5 ports will be sequential, and the remaining n-5 will be
111 The first 5 ports will be sequential, and the remaining n-5 will be
112 randomly selected in the range [port-2*n, port+2*n].
112 randomly selected in the range [port-2*n, port+2*n].
113 """
113 """
114 for i in range(min(5, n)):
114 for i in range(min(5, n)):
115 yield port + i
115 yield port + i
116 for i in range(n-5):
116 for i in range(n-5):
117 yield port + random.randint(-2*n, 2*n)
117 yield port + random.randint(-2*n, 2*n)
118
118
119 def load_handlers(name):
119 def load_handlers(name):
120 """Load the (URL pattern, handler) tuples for each component."""
120 """Load the (URL pattern, handler) tuples for each component."""
121 name = 'IPython.html.' + name
121 name = 'IPython.html.' + name
122 mod = __import__(name, fromlist=['default_handlers'])
122 mod = __import__(name, fromlist=['default_handlers'])
123 return mod.default_handlers
123 return mod.default_handlers
124
124
125 #-----------------------------------------------------------------------------
125 #-----------------------------------------------------------------------------
126 # The Tornado web application
126 # The Tornado web application
127 #-----------------------------------------------------------------------------
127 #-----------------------------------------------------------------------------
128
128
129 class NotebookWebApplication(web.Application):
129 class NotebookWebApplication(web.Application):
130
130
131 def __init__(self, ipython_app, kernel_manager, notebook_manager,
131 def __init__(self, ipython_app, kernel_manager, notebook_manager,
132 cluster_manager, log,
132 cluster_manager, log,
133 base_project_url, settings_overrides):
133 base_project_url, settings_overrides):
134
134
135 settings = self.init_settings(
135 settings = self.init_settings(
136 ipython_app, kernel_manager, notebook_manager, cluster_manager,
136 ipython_app, kernel_manager, notebook_manager, cluster_manager,
137 log, base_project_url, settings_overrides)
137 log, base_project_url, settings_overrides)
138 handlers = self.init_handlers(settings)
138 handlers = self.init_handlers(settings)
139
139
140 super(NotebookWebApplication, self).__init__(handlers, **settings)
140 super(NotebookWebApplication, self).__init__(handlers, **settings)
141
141
142 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
142 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
143 cluster_manager, log,
143 cluster_manager, log,
144 base_project_url, settings_overrides):
144 base_project_url, settings_overrides):
145 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
145 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
146 # base_project_url will always be unicode, which will in turn
146 # base_project_url will always be unicode, which will in turn
147 # make the patterns unicode, and ultimately result in unicode
147 # make the patterns unicode, and ultimately result in unicode
148 # keys in kwargs to handler._execute(**kwargs) in tornado.
148 # keys in kwargs to handler._execute(**kwargs) in tornado.
149 # This enforces that base_project_url be ascii in that situation.
149 # This enforces that base_project_url be ascii in that situation.
150 #
150 #
151 # Note that the URLs these patterns check against are escaped,
151 # Note that the URLs these patterns check against are escaped,
152 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
152 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
153 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
153 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
154 template_path = os.path.join(os.path.dirname(__file__), "templates")
154 template_path = os.path.join(os.path.dirname(__file__), "templates")
155 settings = dict(
155 settings = dict(
156 # basics
156 # basics
157 base_project_url=base_project_url,
157 base_project_url=base_project_url,
158 base_kernel_url=ipython_app.base_kernel_url,
158 base_kernel_url=ipython_app.base_kernel_url,
159 template_path=template_path,
159 template_path=template_path,
160 static_path=ipython_app.static_file_path,
160 static_path=ipython_app.static_file_path,
161 static_handler_class = FileFindHandler,
161 static_handler_class = FileFindHandler,
162 static_url_prefix = url_path_join(base_project_url,'/static/'),
162 static_url_prefix = url_path_join(base_project_url,'/static/'),
163
163
164 # authentication
164 # authentication
165 cookie_secret=ipython_app.cookie_secret,
165 cookie_secret=ipython_app.cookie_secret,
166 login_url=url_path_join(base_project_url,'/login'),
166 login_url=url_path_join(base_project_url,'/login'),
167 read_only=ipython_app.read_only,
168 password=ipython_app.password,
167 password=ipython_app.password,
169
168
170 # managers
169 # managers
171 kernel_manager=kernel_manager,
170 kernel_manager=kernel_manager,
172 notebook_manager=notebook_manager,
171 notebook_manager=notebook_manager,
173 cluster_manager=cluster_manager,
172 cluster_manager=cluster_manager,
174
173
175 # IPython stuff
174 # IPython stuff
176 mathjax_url=ipython_app.mathjax_url,
175 mathjax_url=ipython_app.mathjax_url,
177 config=ipython_app.config,
176 config=ipython_app.config,
178 use_less=ipython_app.use_less,
177 use_less=ipython_app.use_less,
179 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
178 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
180 )
179 )
181
180
182 # allow custom overrides for the tornado web app.
181 # allow custom overrides for the tornado web app.
183 settings.update(settings_overrides)
182 settings.update(settings_overrides)
184 return settings
183 return settings
185
184
186 def init_handlers(self, settings):
185 def init_handlers(self, settings):
187 # Load the (URL pattern, handler) tuples for each component.
186 # Load the (URL pattern, handler) tuples for each component.
188 handlers = []
187 handlers = []
189 handlers.extend(load_handlers('base.handlers'))
188 handlers.extend(load_handlers('base.handlers'))
190 handlers.extend(load_handlers('tree.handlers'))
189 handlers.extend(load_handlers('tree.handlers'))
191 handlers.extend(load_handlers('auth.login'))
190 handlers.extend(load_handlers('auth.login'))
192 handlers.extend(load_handlers('auth.logout'))
191 handlers.extend(load_handlers('auth.logout'))
193 handlers.extend(load_handlers('notebook.handlers'))
192 handlers.extend(load_handlers('notebook.handlers'))
194 handlers.extend(load_handlers('services.kernels.handlers'))
193 handlers.extend(load_handlers('services.kernels.handlers'))
195 handlers.extend(load_handlers('services.notebooks.handlers'))
194 handlers.extend(load_handlers('services.notebooks.handlers'))
196 handlers.extend(load_handlers('services.clusters.handlers'))
195 handlers.extend(load_handlers('services.clusters.handlers'))
197 handlers.extend([
196 handlers.extend([
198 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
197 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
199 ])
198 ])
200 # prepend base_project_url onto the patterns that we match
199 # prepend base_project_url onto the patterns that we match
201 new_handlers = []
200 new_handlers = []
202 for handler in handlers:
201 for handler in handlers:
203 pattern = url_path_join(settings['base_project_url'], handler[0])
202 pattern = url_path_join(settings['base_project_url'], handler[0])
204 new_handler = tuple([pattern] + list(handler[1:]))
203 new_handler = tuple([pattern] + list(handler[1:]))
205 new_handlers.append(new_handler)
204 new_handlers.append(new_handler)
206 return new_handlers
205 return new_handlers
207
206
208
207
209
208
210 #-----------------------------------------------------------------------------
209 #-----------------------------------------------------------------------------
211 # Aliases and Flags
210 # Aliases and Flags
212 #-----------------------------------------------------------------------------
211 #-----------------------------------------------------------------------------
213
212
214 flags = dict(kernel_flags)
213 flags = dict(kernel_flags)
215 flags['no-browser']=(
214 flags['no-browser']=(
216 {'NotebookApp' : {'open_browser' : False}},
215 {'NotebookApp' : {'open_browser' : False}},
217 "Don't open the notebook in a browser after startup."
216 "Don't open the notebook in a browser after startup."
218 )
217 )
219 flags['no-mathjax']=(
218 flags['no-mathjax']=(
220 {'NotebookApp' : {'enable_mathjax' : False}},
219 {'NotebookApp' : {'enable_mathjax' : False}},
221 """Disable MathJax
220 """Disable MathJax
222
221
223 MathJax is the javascript library IPython uses to render math/LaTeX. It is
222 MathJax is the javascript library IPython uses to render math/LaTeX. It is
224 very large, so you may want to disable it if you have a slow internet
223 very large, so you may want to disable it if you have a slow internet
225 connection, or for offline use of the notebook.
224 connection, or for offline use of the notebook.
226
225
227 When disabled, equations etc. will appear as their untransformed TeX source.
226 When disabled, equations etc. will appear as their untransformed TeX source.
228 """
227 """
229 )
228 )
230 flags['read-only'] = (
231 {'NotebookApp' : {'read_only' : True}},
232 """Allow read-only access to notebooks.
233
234 When using a password to protect the notebook server, this flag
235 allows unauthenticated clients to view the notebook list, and
236 individual notebooks, but not edit them, start kernels, or run
237 code.
238
239 If no password is set, the server will be entirely read-only.
240 """
241 )
242
229
243 # Add notebook manager flags
230 # Add notebook manager flags
244 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
231 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
245 'Auto-save a .py script everytime the .ipynb notebook is saved',
232 'Auto-save a .py script everytime the .ipynb notebook is saved',
246 'Do not auto-save .py scripts for every notebook'))
233 'Do not auto-save .py scripts for every notebook'))
247
234
248 # the flags that are specific to the frontend
235 # the flags that are specific to the frontend
249 # these must be scrubbed before being passed to the kernel,
236 # these must be scrubbed before being passed to the kernel,
250 # or it will raise an error on unrecognized flags
237 # or it will raise an error on unrecognized flags
251 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
238 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
252
239
253 aliases = dict(kernel_aliases)
240 aliases = dict(kernel_aliases)
254
241
255 aliases.update({
242 aliases.update({
256 'ip': 'NotebookApp.ip',
243 'ip': 'NotebookApp.ip',
257 'port': 'NotebookApp.port',
244 'port': 'NotebookApp.port',
258 'port-retries': 'NotebookApp.port_retries',
245 'port-retries': 'NotebookApp.port_retries',
259 'transport': 'KernelManager.transport',
246 'transport': 'KernelManager.transport',
260 'keyfile': 'NotebookApp.keyfile',
247 'keyfile': 'NotebookApp.keyfile',
261 'certfile': 'NotebookApp.certfile',
248 'certfile': 'NotebookApp.certfile',
262 'notebook-dir': 'NotebookManager.notebook_dir',
249 'notebook-dir': 'NotebookManager.notebook_dir',
263 'browser': 'NotebookApp.browser',
250 'browser': 'NotebookApp.browser',
264 })
251 })
265
252
266 # remove ipkernel flags that are singletons, and don't make sense in
253 # remove ipkernel flags that are singletons, and don't make sense in
267 # multi-kernel evironment:
254 # multi-kernel evironment:
268 aliases.pop('f', None)
255 aliases.pop('f', None)
269
256
270 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
257 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
271 u'notebook-dir']
258 u'notebook-dir']
272
259
273 #-----------------------------------------------------------------------------
260 #-----------------------------------------------------------------------------
274 # NotebookApp
261 # NotebookApp
275 #-----------------------------------------------------------------------------
262 #-----------------------------------------------------------------------------
276
263
277 class NotebookApp(BaseIPythonApplication):
264 class NotebookApp(BaseIPythonApplication):
278
265
279 name = 'ipython-notebook'
266 name = 'ipython-notebook'
280
267
281 description = """
268 description = """
282 The IPython HTML Notebook.
269 The IPython HTML Notebook.
283
270
284 This launches a Tornado based HTML Notebook Server that serves up an
271 This launches a Tornado based HTML Notebook Server that serves up an
285 HTML5/Javascript Notebook client.
272 HTML5/Javascript Notebook client.
286 """
273 """
287 examples = _examples
274 examples = _examples
288
275
289 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
276 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
290 FileNotebookManager]
277 FileNotebookManager]
291 flags = Dict(flags)
278 flags = Dict(flags)
292 aliases = Dict(aliases)
279 aliases = Dict(aliases)
293
280
294 kernel_argv = List(Unicode)
281 kernel_argv = List(Unicode)
295
282
296 def _log_level_default(self):
283 def _log_level_default(self):
297 return logging.INFO
284 return logging.INFO
298
285
299 def _log_format_default(self):
286 def _log_format_default(self):
300 """override default log format to include time"""
287 """override default log format to include time"""
301 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
288 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
302
289
303 # create requested profiles by default, if they don't exist:
290 # create requested profiles by default, if they don't exist:
304 auto_create = Bool(True)
291 auto_create = Bool(True)
305
292
306 # file to be opened in the notebook server
293 # file to be opened in the notebook server
307 file_to_run = Unicode('')
294 file_to_run = Unicode('')
308
295
309 # Network related information.
296 # Network related information.
310
297
311 ip = Unicode(LOCALHOST, config=True,
298 ip = Unicode(LOCALHOST, config=True,
312 help="The IP address the notebook server will listen on."
299 help="The IP address the notebook server will listen on."
313 )
300 )
314
301
315 def _ip_changed(self, name, old, new):
302 def _ip_changed(self, name, old, new):
316 if new == u'*': self.ip = u''
303 if new == u'*': self.ip = u''
317
304
318 port = Integer(8888, config=True,
305 port = Integer(8888, config=True,
319 help="The port the notebook server will listen on."
306 help="The port the notebook server will listen on."
320 )
307 )
321 port_retries = Integer(50, config=True,
308 port_retries = Integer(50, config=True,
322 help="The number of additional ports to try if the specified port is not available."
309 help="The number of additional ports to try if the specified port is not available."
323 )
310 )
324
311
325 certfile = Unicode(u'', config=True,
312 certfile = Unicode(u'', config=True,
326 help="""The full path to an SSL/TLS certificate file."""
313 help="""The full path to an SSL/TLS certificate file."""
327 )
314 )
328
315
329 keyfile = Unicode(u'', config=True,
316 keyfile = Unicode(u'', config=True,
330 help="""The full path to a private key file for usage with SSL/TLS."""
317 help="""The full path to a private key file for usage with SSL/TLS."""
331 )
318 )
332
319
333 cookie_secret = Bytes(b'', config=True,
320 cookie_secret = Bytes(b'', config=True,
334 help="""The random bytes used to secure cookies.
321 help="""The random bytes used to secure cookies.
335 By default this is a new random number every time you start the Notebook.
322 By default this is a new random number every time you start the Notebook.
336 Set it to a value in a config file to enable logins to persist across server sessions.
323 Set it to a value in a config file to enable logins to persist across server sessions.
337
324
338 Note: Cookie secrets should be kept private, do not share config files with
325 Note: Cookie secrets should be kept private, do not share config files with
339 cookie_secret stored in plaintext (you can read the value from a file).
326 cookie_secret stored in plaintext (you can read the value from a file).
340 """
327 """
341 )
328 )
342 def _cookie_secret_default(self):
329 def _cookie_secret_default(self):
343 return os.urandom(1024)
330 return os.urandom(1024)
344
331
345 password = Unicode(u'', config=True,
332 password = Unicode(u'', config=True,
346 help="""Hashed password to use for web authentication.
333 help="""Hashed password to use for web authentication.
347
334
348 To generate, type in a python/IPython shell:
335 To generate, type in a python/IPython shell:
349
336
350 from IPython.lib import passwd; passwd()
337 from IPython.lib import passwd; passwd()
351
338
352 The string should be of the form type:salt:hashed-password.
339 The string should be of the form type:salt:hashed-password.
353 """
340 """
354 )
341 )
355
342
356 open_browser = Bool(True, config=True,
343 open_browser = Bool(True, config=True,
357 help="""Whether to open in a browser after starting.
344 help="""Whether to open in a browser after starting.
358 The specific browser used is platform dependent and
345 The specific browser used is platform dependent and
359 determined by the python standard library `webbrowser`
346 determined by the python standard library `webbrowser`
360 module, unless it is overridden using the --browser
347 module, unless it is overridden using the --browser
361 (NotebookApp.browser) configuration option.
348 (NotebookApp.browser) configuration option.
362 """)
349 """)
363
350
364 browser = Unicode(u'', config=True,
351 browser = Unicode(u'', config=True,
365 help="""Specify what command to use to invoke a web
352 help="""Specify what command to use to invoke a web
366 browser when opening the notebook. If not specified, the
353 browser when opening the notebook. If not specified, the
367 default browser will be determined by the `webbrowser`
354 default browser will be determined by the `webbrowser`
368 standard library module, which allows setting of the
355 standard library module, which allows setting of the
369 BROWSER environment variable to override it.
356 BROWSER environment variable to override it.
370 """)
357 """)
371
358
372 read_only = Bool(False, config=True,
373 help="Whether to prevent editing/execution of notebooks."
374 )
375
376 use_less = Bool(False, config=True,
359 use_less = Bool(False, config=True,
377 help="""Wether to use Browser Side less-css parsing
360 help="""Wether to use Browser Side less-css parsing
378 instead of compiled css version in templates that allows
361 instead of compiled css version in templates that allows
379 it. This is mainly convenient when working on the less
362 it. This is mainly convenient when working on the less
380 file to avoid a build step, or if user want to overwrite
363 file to avoid a build step, or if user want to overwrite
381 some of the less variables without having to recompile
364 some of the less variables without having to recompile
382 everything.
365 everything.
383
366
384 You will need to install the less.js component in the static directory
367 You will need to install the less.js component in the static directory
385 either in the source tree or in your profile folder.
368 either in the source tree or in your profile folder.
386 """)
369 """)
387
370
388 webapp_settings = Dict(config=True,
371 webapp_settings = Dict(config=True,
389 help="Supply overrides for the tornado.web.Application that the "
372 help="Supply overrides for the tornado.web.Application that the "
390 "IPython notebook uses.")
373 "IPython notebook uses.")
391
374
392 enable_mathjax = Bool(True, config=True,
375 enable_mathjax = Bool(True, config=True,
393 help="""Whether to enable MathJax for typesetting math/TeX
376 help="""Whether to enable MathJax for typesetting math/TeX
394
377
395 MathJax is the javascript library IPython uses to render math/LaTeX. It is
378 MathJax is the javascript library IPython uses to render math/LaTeX. It is
396 very large, so you may want to disable it if you have a slow internet
379 very large, so you may want to disable it if you have a slow internet
397 connection, or for offline use of the notebook.
380 connection, or for offline use of the notebook.
398
381
399 When disabled, equations etc. will appear as their untransformed TeX source.
382 When disabled, equations etc. will appear as their untransformed TeX source.
400 """
383 """
401 )
384 )
402 def _enable_mathjax_changed(self, name, old, new):
385 def _enable_mathjax_changed(self, name, old, new):
403 """set mathjax url to empty if mathjax is disabled"""
386 """set mathjax url to empty if mathjax is disabled"""
404 if not new:
387 if not new:
405 self.mathjax_url = u''
388 self.mathjax_url = u''
406
389
407 base_project_url = Unicode('/', config=True,
390 base_project_url = Unicode('/', config=True,
408 help='''The base URL for the notebook server.
391 help='''The base URL for the notebook server.
409
392
410 Leading and trailing slashes can be omitted,
393 Leading and trailing slashes can be omitted,
411 and will automatically be added.
394 and will automatically be added.
412 ''')
395 ''')
413 def _base_project_url_changed(self, name, old, new):
396 def _base_project_url_changed(self, name, old, new):
414 if not new.startswith('/'):
397 if not new.startswith('/'):
415 self.base_project_url = '/'+new
398 self.base_project_url = '/'+new
416 elif not new.endswith('/'):
399 elif not new.endswith('/'):
417 self.base_project_url = new+'/'
400 self.base_project_url = new+'/'
418
401
419 base_kernel_url = Unicode('/', config=True,
402 base_kernel_url = Unicode('/', config=True,
420 help='''The base URL for the kernel server
403 help='''The base URL for the kernel server
421
404
422 Leading and trailing slashes can be omitted,
405 Leading and trailing slashes can be omitted,
423 and will automatically be added.
406 and will automatically be added.
424 ''')
407 ''')
425 def _base_kernel_url_changed(self, name, old, new):
408 def _base_kernel_url_changed(self, name, old, new):
426 if not new.startswith('/'):
409 if not new.startswith('/'):
427 self.base_kernel_url = '/'+new
410 self.base_kernel_url = '/'+new
428 elif not new.endswith('/'):
411 elif not new.endswith('/'):
429 self.base_kernel_url = new+'/'
412 self.base_kernel_url = new+'/'
430
413
431 websocket_url = Unicode("", config=True,
414 websocket_url = Unicode("", config=True,
432 help="""The base URL for the websocket server,
415 help="""The base URL for the websocket server,
433 if it differs from the HTTP server (hint: it almost certainly doesn't).
416 if it differs from the HTTP server (hint: it almost certainly doesn't).
434
417
435 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
418 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
436 """
419 """
437 )
420 )
438
421
439 extra_static_paths = List(Unicode, config=True,
422 extra_static_paths = List(Unicode, config=True,
440 help="""Extra paths to search for serving static files.
423 help="""Extra paths to search for serving static files.
441
424
442 This allows adding javascript/css to be available from the notebook server machine,
425 This allows adding javascript/css to be available from the notebook server machine,
443 or overriding individual files in the IPython"""
426 or overriding individual files in the IPython"""
444 )
427 )
445 def _extra_static_paths_default(self):
428 def _extra_static_paths_default(self):
446 return [os.path.join(self.profile_dir.location, 'static')]
429 return [os.path.join(self.profile_dir.location, 'static')]
447
430
448 @property
431 @property
449 def static_file_path(self):
432 def static_file_path(self):
450 """return extra paths + the default location"""
433 """return extra paths + the default location"""
451 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
434 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
452
435
453 mathjax_url = Unicode("", config=True,
436 mathjax_url = Unicode("", config=True,
454 help="""The url for MathJax.js."""
437 help="""The url for MathJax.js."""
455 )
438 )
456 def _mathjax_url_default(self):
439 def _mathjax_url_default(self):
457 if not self.enable_mathjax:
440 if not self.enable_mathjax:
458 return u''
441 return u''
459 static_url_prefix = self.webapp_settings.get("static_url_prefix",
442 static_url_prefix = self.webapp_settings.get("static_url_prefix",
460 url_path_join(self.base_project_url, "static")
443 url_path_join(self.base_project_url, "static")
461 )
444 )
462 try:
445 try:
463 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
446 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
464 except IOError:
447 except IOError:
465 if self.certfile:
448 if self.certfile:
466 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
449 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
467 base = u"https://c328740.ssl.cf1.rackcdn.com"
450 base = u"https://c328740.ssl.cf1.rackcdn.com"
468 else:
451 else:
469 base = u"http://cdn.mathjax.org"
452 base = u"http://cdn.mathjax.org"
470
453
471 url = base + u"/mathjax/latest/MathJax.js"
454 url = base + u"/mathjax/latest/MathJax.js"
472 self.log.info("Using MathJax from CDN: %s", url)
455 self.log.info("Using MathJax from CDN: %s", url)
473 return url
456 return url
474 else:
457 else:
475 self.log.info("Using local MathJax from %s" % mathjax)
458 self.log.info("Using local MathJax from %s" % mathjax)
476 return url_path_join(static_url_prefix, u"mathjax/MathJax.js")
459 return url_path_join(static_url_prefix, u"mathjax/MathJax.js")
477
460
478 def _mathjax_url_changed(self, name, old, new):
461 def _mathjax_url_changed(self, name, old, new):
479 if new and not self.enable_mathjax:
462 if new and not self.enable_mathjax:
480 # enable_mathjax=False overrides mathjax_url
463 # enable_mathjax=False overrides mathjax_url
481 self.mathjax_url = u''
464 self.mathjax_url = u''
482 else:
465 else:
483 self.log.info("Using MathJax: %s", new)
466 self.log.info("Using MathJax: %s", new)
484
467
485 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
468 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
486 config=True,
469 config=True,
487 help='The notebook manager class to use.')
470 help='The notebook manager class to use.')
488
471
489 trust_xheaders = Bool(False, config=True,
472 trust_xheaders = Bool(False, config=True,
490 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
473 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
491 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
474 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
492 )
475 )
493
476
494 def parse_command_line(self, argv=None):
477 def parse_command_line(self, argv=None):
495 super(NotebookApp, self).parse_command_line(argv)
478 super(NotebookApp, self).parse_command_line(argv)
496 if argv is None:
479 if argv is None:
497 argv = sys.argv[1:]
480 argv = sys.argv[1:]
498
481
499 # Scrub frontend-specific flags
482 # Scrub frontend-specific flags
500 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
483 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
501 # Kernel should inherit default config file from frontend
484 # Kernel should inherit default config file from frontend
502 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
485 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
503
486
504 if self.extra_args:
487 if self.extra_args:
505 f = os.path.abspath(self.extra_args[0])
488 f = os.path.abspath(self.extra_args[0])
506 if os.path.isdir(f):
489 if os.path.isdir(f):
507 nbdir = f
490 nbdir = f
508 else:
491 else:
509 self.file_to_run = f
492 self.file_to_run = f
510 nbdir = os.path.dirname(f)
493 nbdir = os.path.dirname(f)
511 self.config.NotebookManager.notebook_dir = nbdir
494 self.config.NotebookManager.notebook_dir = nbdir
512
495
513 def init_configurables(self):
496 def init_configurables(self):
514 # force Session default to be secure
497 # force Session default to be secure
515 default_secure(self.config)
498 default_secure(self.config)
516 self.kernel_manager = MappingKernelManager(
499 self.kernel_manager = MappingKernelManager(
517 parent=self, log=self.log, kernel_argv=self.kernel_argv,
500 parent=self, log=self.log, kernel_argv=self.kernel_argv,
518 connection_dir = self.profile_dir.security_dir,
501 connection_dir = self.profile_dir.security_dir,
519 )
502 )
520 kls = import_item(self.notebook_manager_class)
503 kls = import_item(self.notebook_manager_class)
521 self.notebook_manager = kls(parent=self, log=self.log)
504 self.notebook_manager = kls(parent=self, log=self.log)
522 self.notebook_manager.load_notebook_names()
505 self.notebook_manager.load_notebook_names()
523 self.cluster_manager = ClusterManager(parent=self, log=self.log)
506 self.cluster_manager = ClusterManager(parent=self, log=self.log)
524 self.cluster_manager.update_profiles()
507 self.cluster_manager.update_profiles()
525
508
526 def init_logging(self):
509 def init_logging(self):
527 # This prevents double log messages because tornado use a root logger that
510 # This prevents double log messages because tornado use a root logger that
528 # self.log is a child of. The logging module dipatches log messages to a log
511 # self.log is a child of. The logging module dipatches log messages to a log
529 # and all of its ancenstors until propagate is set to False.
512 # and all of its ancenstors until propagate is set to False.
530 self.log.propagate = False
513 self.log.propagate = False
531
514
532 # hook up tornado 3's loggers to our app handlers
515 # hook up tornado 3's loggers to our app handlers
533 for name in ('access', 'application', 'general'):
516 for name in ('access', 'application', 'general'):
534 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
517 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
535
518
536 def init_webapp(self):
519 def init_webapp(self):
537 """initialize tornado webapp and httpserver"""
520 """initialize tornado webapp and httpserver"""
538 self.web_app = NotebookWebApplication(
521 self.web_app = NotebookWebApplication(
539 self, self.kernel_manager, self.notebook_manager,
522 self, self.kernel_manager, self.notebook_manager,
540 self.cluster_manager, self.log,
523 self.cluster_manager, self.log,
541 self.base_project_url, self.webapp_settings
524 self.base_project_url, self.webapp_settings
542 )
525 )
543 if self.certfile:
526 if self.certfile:
544 ssl_options = dict(certfile=self.certfile)
527 ssl_options = dict(certfile=self.certfile)
545 if self.keyfile:
528 if self.keyfile:
546 ssl_options['keyfile'] = self.keyfile
529 ssl_options['keyfile'] = self.keyfile
547 else:
530 else:
548 ssl_options = None
531 ssl_options = None
549 self.web_app.password = self.password
532 self.web_app.password = self.password
550 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
533 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
551 xheaders=self.trust_xheaders)
534 xheaders=self.trust_xheaders)
552 if not self.ip:
535 if not self.ip:
553 warning = "WARNING: The notebook server is listening on all IP addresses"
536 warning = "WARNING: The notebook server is listening on all IP addresses"
554 if ssl_options is None:
537 if ssl_options is None:
555 self.log.critical(warning + " and not using encryption. This "
538 self.log.critical(warning + " and not using encryption. This "
556 "is not recommended.")
539 "is not recommended.")
557 if not self.password and not self.read_only:
540 if not self.password:
558 self.log.critical(warning + " and not using authentication. "
541 self.log.critical(warning + " and not using authentication. "
559 "This is highly insecure and not recommended.")
542 "This is highly insecure and not recommended.")
560 success = None
543 success = None
561 for port in random_ports(self.port, self.port_retries+1):
544 for port in random_ports(self.port, self.port_retries+1):
562 try:
545 try:
563 self.http_server.listen(port, self.ip)
546 self.http_server.listen(port, self.ip)
564 except socket.error as e:
547 except socket.error as e:
565 # XXX: remove the e.errno == -9 block when we require
548 # XXX: remove the e.errno == -9 block when we require
566 # tornado >= 3.0
549 # tornado >= 3.0
567 if e.errno == -9 and tornado.version_info[0] < 3:
550 if e.errno == -9 and tornado.version_info[0] < 3:
568 # The flags passed to socket.getaddrinfo from
551 # The flags passed to socket.getaddrinfo from
569 # tornado.netutils.bind_sockets can cause "gaierror:
552 # tornado.netutils.bind_sockets can cause "gaierror:
570 # [Errno -9] Address family for hostname not supported"
553 # [Errno -9] Address family for hostname not supported"
571 # when the interface is not associated, for example.
554 # when the interface is not associated, for example.
572 # Changing the flags to exclude socket.AI_ADDRCONFIG does
555 # Changing the flags to exclude socket.AI_ADDRCONFIG does
573 # not cause this error, but the only way to do this is to
556 # not cause this error, but the only way to do this is to
574 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
557 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
575 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
558 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
576 self.log.warn('Monkeypatching socket to fix tornado bug')
559 self.log.warn('Monkeypatching socket to fix tornado bug')
577 del(socket.AI_ADDRCONFIG)
560 del(socket.AI_ADDRCONFIG)
578 try:
561 try:
579 # retry the tornado call without AI_ADDRCONFIG flags
562 # retry the tornado call without AI_ADDRCONFIG flags
580 self.http_server.listen(port, self.ip)
563 self.http_server.listen(port, self.ip)
581 except socket.error as e2:
564 except socket.error as e2:
582 e = e2
565 e = e2
583 else:
566 else:
584 self.port = port
567 self.port = port
585 success = True
568 success = True
586 break
569 break
587 # restore the monekypatch
570 # restore the monekypatch
588 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
571 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
589 if e.errno != errno.EADDRINUSE:
572 if e.errno != errno.EADDRINUSE:
590 raise
573 raise
591 self.log.info('The port %i is already in use, trying another random port.' % port)
574 self.log.info('The port %i is already in use, trying another random port.' % port)
592 else:
575 else:
593 self.port = port
576 self.port = port
594 success = True
577 success = True
595 break
578 break
596 if not success:
579 if not success:
597 self.log.critical('ERROR: the notebook server could not be started because '
580 self.log.critical('ERROR: the notebook server could not be started because '
598 'no available port could be found.')
581 'no available port could be found.')
599 self.exit(1)
582 self.exit(1)
600
583
601 def init_signal(self):
584 def init_signal(self):
602 if not sys.platform.startswith('win'):
585 if not sys.platform.startswith('win'):
603 signal.signal(signal.SIGINT, self._handle_sigint)
586 signal.signal(signal.SIGINT, self._handle_sigint)
604 signal.signal(signal.SIGTERM, self._signal_stop)
587 signal.signal(signal.SIGTERM, self._signal_stop)
605 if hasattr(signal, 'SIGUSR1'):
588 if hasattr(signal, 'SIGUSR1'):
606 # Windows doesn't support SIGUSR1
589 # Windows doesn't support SIGUSR1
607 signal.signal(signal.SIGUSR1, self._signal_info)
590 signal.signal(signal.SIGUSR1, self._signal_info)
608 if hasattr(signal, 'SIGINFO'):
591 if hasattr(signal, 'SIGINFO'):
609 # only on BSD-based systems
592 # only on BSD-based systems
610 signal.signal(signal.SIGINFO, self._signal_info)
593 signal.signal(signal.SIGINFO, self._signal_info)
611
594
612 def _handle_sigint(self, sig, frame):
595 def _handle_sigint(self, sig, frame):
613 """SIGINT handler spawns confirmation dialog"""
596 """SIGINT handler spawns confirmation dialog"""
614 # register more forceful signal handler for ^C^C case
597 # register more forceful signal handler for ^C^C case
615 signal.signal(signal.SIGINT, self._signal_stop)
598 signal.signal(signal.SIGINT, self._signal_stop)
616 # request confirmation dialog in bg thread, to avoid
599 # request confirmation dialog in bg thread, to avoid
617 # blocking the App
600 # blocking the App
618 thread = threading.Thread(target=self._confirm_exit)
601 thread = threading.Thread(target=self._confirm_exit)
619 thread.daemon = True
602 thread.daemon = True
620 thread.start()
603 thread.start()
621
604
622 def _restore_sigint_handler(self):
605 def _restore_sigint_handler(self):
623 """callback for restoring original SIGINT handler"""
606 """callback for restoring original SIGINT handler"""
624 signal.signal(signal.SIGINT, self._handle_sigint)
607 signal.signal(signal.SIGINT, self._handle_sigint)
625
608
626 def _confirm_exit(self):
609 def _confirm_exit(self):
627 """confirm shutdown on ^C
610 """confirm shutdown on ^C
628
611
629 A second ^C, or answering 'y' within 5s will cause shutdown,
612 A second ^C, or answering 'y' within 5s will cause shutdown,
630 otherwise original SIGINT handler will be restored.
613 otherwise original SIGINT handler will be restored.
631
614
632 This doesn't work on Windows.
615 This doesn't work on Windows.
633 """
616 """
634 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
617 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
635 time.sleep(0.1)
618 time.sleep(0.1)
636 info = self.log.info
619 info = self.log.info
637 info('interrupted')
620 info('interrupted')
638 print self.notebook_info()
621 print self.notebook_info()
639 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
622 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
640 sys.stdout.flush()
623 sys.stdout.flush()
641 r,w,x = select.select([sys.stdin], [], [], 5)
624 r,w,x = select.select([sys.stdin], [], [], 5)
642 if r:
625 if r:
643 line = sys.stdin.readline()
626 line = sys.stdin.readline()
644 if line.lower().startswith('y'):
627 if line.lower().startswith('y'):
645 self.log.critical("Shutdown confirmed")
628 self.log.critical("Shutdown confirmed")
646 ioloop.IOLoop.instance().stop()
629 ioloop.IOLoop.instance().stop()
647 return
630 return
648 else:
631 else:
649 print "No answer for 5s:",
632 print "No answer for 5s:",
650 print "resuming operation..."
633 print "resuming operation..."
651 # no answer, or answer is no:
634 # no answer, or answer is no:
652 # set it back to original SIGINT handler
635 # set it back to original SIGINT handler
653 # use IOLoop.add_callback because signal.signal must be called
636 # use IOLoop.add_callback because signal.signal must be called
654 # from main thread
637 # from main thread
655 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
638 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
656
639
657 def _signal_stop(self, sig, frame):
640 def _signal_stop(self, sig, frame):
658 self.log.critical("received signal %s, stopping", sig)
641 self.log.critical("received signal %s, stopping", sig)
659 ioloop.IOLoop.instance().stop()
642 ioloop.IOLoop.instance().stop()
660
643
661 def _signal_info(self, sig, frame):
644 def _signal_info(self, sig, frame):
662 print self.notebook_info()
645 print self.notebook_info()
663
646
664 def init_components(self):
647 def init_components(self):
665 """Check the components submodule, and warn if it's unclean"""
648 """Check the components submodule, and warn if it's unclean"""
666 status = submodule.check_submodule_status()
649 status = submodule.check_submodule_status()
667 if status == 'missing':
650 if status == 'missing':
668 self.log.warn("components submodule missing, running `git submodule update`")
651 self.log.warn("components submodule missing, running `git submodule update`")
669 submodule.update_submodules(submodule.ipython_parent())
652 submodule.update_submodules(submodule.ipython_parent())
670 elif status == 'unclean':
653 elif status == 'unclean':
671 self.log.warn("components submodule unclean, you may see 404s on static/components")
654 self.log.warn("components submodule unclean, you may see 404s on static/components")
672 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
655 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
673
656
674
657
675 @catch_config_error
658 @catch_config_error
676 def initialize(self, argv=None):
659 def initialize(self, argv=None):
677 self.init_logging()
660 self.init_logging()
678 super(NotebookApp, self).initialize(argv)
661 super(NotebookApp, self).initialize(argv)
679 self.init_configurables()
662 self.init_configurables()
680 self.init_components()
663 self.init_components()
681 self.init_webapp()
664 self.init_webapp()
682 self.init_signal()
665 self.init_signal()
683
666
684 def cleanup_kernels(self):
667 def cleanup_kernels(self):
685 """Shutdown all kernels.
668 """Shutdown all kernels.
686
669
687 The kernels will shutdown themselves when this process no longer exists,
670 The kernels will shutdown themselves when this process no longer exists,
688 but explicit shutdown allows the KernelManagers to cleanup the connection files.
671 but explicit shutdown allows the KernelManagers to cleanup the connection files.
689 """
672 """
690 self.log.info('Shutting down kernels')
673 self.log.info('Shutting down kernels')
691 self.kernel_manager.shutdown_all()
674 self.kernel_manager.shutdown_all()
692
675
693 def notebook_info(self):
676 def notebook_info(self):
694 "Return the current working directory and the server url information"
677 "Return the current working directory and the server url information"
695 mgr_info = self.notebook_manager.info_string() + "\n"
678 mgr_info = self.notebook_manager.info_string() + "\n"
696 return mgr_info +"The IPython Notebook is running at: %s" % self._url
679 return mgr_info +"The IPython Notebook is running at: %s" % self._url
697
680
698 def start(self):
681 def start(self):
699 """ Start the IPython Notebook server app, after initialization
682 """ Start the IPython Notebook server app, after initialization
700
683
701 This method takes no arguments so all configuration and initialization
684 This method takes no arguments so all configuration and initialization
702 must be done prior to calling this method."""
685 must be done prior to calling this method."""
703 ip = self.ip if self.ip else '[all ip addresses on your system]'
686 ip = self.ip if self.ip else '[all ip addresses on your system]'
704 proto = 'https' if self.certfile else 'http'
687 proto = 'https' if self.certfile else 'http'
705 info = self.log.info
688 info = self.log.info
706 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
689 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
707 self.base_project_url)
690 self.base_project_url)
708 for line in self.notebook_info().split("\n"):
691 for line in self.notebook_info().split("\n"):
709 info(line)
692 info(line)
710 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
693 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
711
694
712 if self.open_browser or self.file_to_run:
695 if self.open_browser or self.file_to_run:
713 ip = self.ip or LOCALHOST
696 ip = self.ip or LOCALHOST
714 try:
697 try:
715 browser = webbrowser.get(self.browser or None)
698 browser = webbrowser.get(self.browser or None)
716 except webbrowser.Error as e:
699 except webbrowser.Error as e:
717 self.log.warn('No web browser found: %s.' % e)
700 self.log.warn('No web browser found: %s.' % e)
718 browser = None
701 browser = None
719
702
720 if self.file_to_run:
703 if self.file_to_run:
721 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
704 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
722 url = self.notebook_manager.rev_mapping.get(name, '')
705 url = self.notebook_manager.rev_mapping.get(name, '')
723 else:
706 else:
724 url = ''
707 url = ''
725 if browser:
708 if browser:
726 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
709 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
727 self.port, self.base_project_url, url), new=2)
710 self.port, self.base_project_url, url), new=2)
728 threading.Thread(target=b).start()
711 threading.Thread(target=b).start()
729 try:
712 try:
730 ioloop.IOLoop.instance().start()
713 ioloop.IOLoop.instance().start()
731 except KeyboardInterrupt:
714 except KeyboardInterrupt:
732 info("Interrupted...")
715 info("Interrupted...")
733 finally:
716 finally:
734 self.cleanup_kernels()
717 self.cleanup_kernels()
735
718
736
719
737 #-----------------------------------------------------------------------------
720 #-----------------------------------------------------------------------------
738 # Main entry point
721 # Main entry point
739 #-----------------------------------------------------------------------------
722 #-----------------------------------------------------------------------------
740
723
741 launch_new_instance = NotebookApp.launch_instance
724 launch_new_instance = NotebookApp.launch_instance
742
725
@@ -1,156 +1,156 b''
1 """Tornado handlers for the notebooks web service.
1 """Tornado handlers for the notebooks web service.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from tornado import web
19 from tornado import web
20
20
21 from zmq.utils import jsonapi
21 from zmq.utils import jsonapi
22
22
23 from IPython.utils.jsonutil import date_default
23 from IPython.utils.jsonutil import date_default
24
24
25 from ...base.handlers import IPythonHandler, authenticate_unless_readonly
25 from ...base.handlers import IPythonHandler
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Notebook web service handlers
28 # Notebook web service handlers
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 class NotebookRootHandler(IPythonHandler):
31 class NotebookRootHandler(IPythonHandler):
32
32
33 @authenticate_unless_readonly
33 @web.authenticated
34 def get(self):
34 def get(self):
35 nbm = self.notebook_manager
35 nbm = self.notebook_manager
36 km = self.kernel_manager
36 km = self.kernel_manager
37 files = nbm.list_notebooks()
37 files = nbm.list_notebooks()
38 for f in files :
38 for f in files :
39 f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
39 f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
40 self.finish(jsonapi.dumps(files))
40 self.finish(jsonapi.dumps(files))
41
41
42 @web.authenticated
42 @web.authenticated
43 def post(self):
43 def post(self):
44 nbm = self.notebook_manager
44 nbm = self.notebook_manager
45 body = self.request.body.strip()
45 body = self.request.body.strip()
46 format = self.get_argument('format', default='json')
46 format = self.get_argument('format', default='json')
47 name = self.get_argument('name', default=None)
47 name = self.get_argument('name', default=None)
48 if body:
48 if body:
49 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
49 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
50 else:
50 else:
51 notebook_id = nbm.new_notebook()
51 notebook_id = nbm.new_notebook()
52 self.set_header('Location', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
52 self.set_header('Location', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
53 self.finish(jsonapi.dumps(notebook_id))
53 self.finish(jsonapi.dumps(notebook_id))
54
54
55
55
56 class NotebookHandler(IPythonHandler):
56 class NotebookHandler(IPythonHandler):
57
57
58 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
58 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
59
59
60 @authenticate_unless_readonly
60 @web.authenticated
61 def get(self, notebook_id):
61 def get(self, notebook_id):
62 nbm = self.notebook_manager
62 nbm = self.notebook_manager
63 format = self.get_argument('format', default='json')
63 format = self.get_argument('format', default='json')
64 last_mod, name, data = nbm.get_notebook(notebook_id, format)
64 last_mod, name, data = nbm.get_notebook(notebook_id, format)
65
65
66 if format == u'json':
66 if format == u'json':
67 self.set_header('Content-Type', 'application/json')
67 self.set_header('Content-Type', 'application/json')
68 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
68 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
69 elif format == u'py':
69 elif format == u'py':
70 self.set_header('Content-Type', 'application/x-python')
70 self.set_header('Content-Type', 'application/x-python')
71 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
71 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
72 self.set_header('Last-Modified', last_mod)
72 self.set_header('Last-Modified', last_mod)
73 self.finish(data)
73 self.finish(data)
74
74
75 @web.authenticated
75 @web.authenticated
76 def put(self, notebook_id):
76 def put(self, notebook_id):
77 nbm = self.notebook_manager
77 nbm = self.notebook_manager
78 format = self.get_argument('format', default='json')
78 format = self.get_argument('format', default='json')
79 name = self.get_argument('name', default=None)
79 name = self.get_argument('name', default=None)
80 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
80 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
81 self.set_status(204)
81 self.set_status(204)
82 self.finish()
82 self.finish()
83
83
84 @web.authenticated
84 @web.authenticated
85 def delete(self, notebook_id):
85 def delete(self, notebook_id):
86 self.notebook_manager.delete_notebook(notebook_id)
86 self.notebook_manager.delete_notebook(notebook_id)
87 self.set_status(204)
87 self.set_status(204)
88 self.finish()
88 self.finish()
89
89
90
90
91 class NotebookCheckpointsHandler(IPythonHandler):
91 class NotebookCheckpointsHandler(IPythonHandler):
92
92
93 SUPPORTED_METHODS = ('GET', 'POST')
93 SUPPORTED_METHODS = ('GET', 'POST')
94
94
95 @web.authenticated
95 @web.authenticated
96 def get(self, notebook_id):
96 def get(self, notebook_id):
97 """get lists checkpoints for a notebook"""
97 """get lists checkpoints for a notebook"""
98 nbm = self.notebook_manager
98 nbm = self.notebook_manager
99 checkpoints = nbm.list_checkpoints(notebook_id)
99 checkpoints = nbm.list_checkpoints(notebook_id)
100 data = jsonapi.dumps(checkpoints, default=date_default)
100 data = jsonapi.dumps(checkpoints, default=date_default)
101 self.finish(data)
101 self.finish(data)
102
102
103 @web.authenticated
103 @web.authenticated
104 def post(self, notebook_id):
104 def post(self, notebook_id):
105 """post creates a new checkpoint"""
105 """post creates a new checkpoint"""
106 nbm = self.notebook_manager
106 nbm = self.notebook_manager
107 checkpoint = nbm.create_checkpoint(notebook_id)
107 checkpoint = nbm.create_checkpoint(notebook_id)
108 data = jsonapi.dumps(checkpoint, default=date_default)
108 data = jsonapi.dumps(checkpoint, default=date_default)
109 self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
109 self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
110 self.base_project_url, notebook_id, checkpoint['checkpoint_id']
110 self.base_project_url, notebook_id, checkpoint['checkpoint_id']
111 ))
111 ))
112
112
113 self.finish(data)
113 self.finish(data)
114
114
115
115
116 class ModifyNotebookCheckpointsHandler(IPythonHandler):
116 class ModifyNotebookCheckpointsHandler(IPythonHandler):
117
117
118 SUPPORTED_METHODS = ('POST', 'DELETE')
118 SUPPORTED_METHODS = ('POST', 'DELETE')
119
119
120 @web.authenticated
120 @web.authenticated
121 def post(self, notebook_id, checkpoint_id):
121 def post(self, notebook_id, checkpoint_id):
122 """post restores a notebook from a checkpoint"""
122 """post restores a notebook from a checkpoint"""
123 nbm = self.notebook_manager
123 nbm = self.notebook_manager
124 nbm.restore_checkpoint(notebook_id, checkpoint_id)
124 nbm.restore_checkpoint(notebook_id, checkpoint_id)
125 self.set_status(204)
125 self.set_status(204)
126 self.finish()
126 self.finish()
127
127
128 @web.authenticated
128 @web.authenticated
129 def delete(self, notebook_id, checkpoint_id):
129 def delete(self, notebook_id, checkpoint_id):
130 """delete clears a checkpoint for a given notebook"""
130 """delete clears a checkpoint for a given notebook"""
131 nbm = self.notebook_manager
131 nbm = self.notebook_manager
132 nbm.delte_checkpoint(notebook_id, checkpoint_id)
132 nbm.delte_checkpoint(notebook_id, checkpoint_id)
133 self.set_status(204)
133 self.set_status(204)
134 self.finish()
134 self.finish()
135
135
136
136
137 #-----------------------------------------------------------------------------
137 #-----------------------------------------------------------------------------
138 # URL to handler mappings
138 # URL to handler mappings
139 #-----------------------------------------------------------------------------
139 #-----------------------------------------------------------------------------
140
140
141
141
142 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
142 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
143 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
143 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
144
144
145 default_handlers = [
145 default_handlers = [
146 (r"/notebooks", NotebookRootHandler),
146 (r"/notebooks", NotebookRootHandler),
147 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
147 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
148 (r"/notebooks/%s/checkpoints" % _notebook_id_regex, NotebookCheckpointsHandler),
148 (r"/notebooks/%s/checkpoints" % _notebook_id_regex, NotebookCheckpointsHandler),
149 (r"/notebooks/%s/checkpoints/%s" % (_notebook_id_regex, _checkpoint_id_regex),
149 (r"/notebooks/%s/checkpoints/%s" % (_notebook_id_regex, _checkpoint_id_regex),
150 ModifyNotebookCheckpointsHandler
150 ModifyNotebookCheckpointsHandler
151 ),
151 ),
152 ]
152 ]
153
153
154
154
155
155
156
156
@@ -1,445 +1,441 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // CodeCell
9 // CodeCell
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule CodeCell
15 * @submodule CodeCell
16 */
16 */
17
17
18
18
19 /* local util for codemirror */
19 /* local util for codemirror */
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}
21
21
22 /**
22 /**
23 *
23 *
24 * function to delete until previous non blanking space character
24 * function to delete until previous non blanking space character
25 * or first multiple of 4 tabstop.
25 * or first multiple of 4 tabstop.
26 * @private
26 * @private
27 */
27 */
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 if (!posEq(from, to)) {cm.replaceRange("", from, to); return}
30 if (!posEq(from, to)) {cm.replaceRange("", from, to); return}
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 var tabsize = cm.getOption('tabSize');
32 var tabsize = cm.getOption('tabSize');
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 var from = {ch:cur.ch-chToPrevTabStop,line:cur.line}
34 var from = {ch:cur.ch-chToPrevTabStop,line:cur.line}
35 var select = cm.getRange(from,cur)
35 var select = cm.getRange(from,cur)
36 if( select.match(/^\ +$/) != null){
36 if( select.match(/^\ +$/) != null){
37 cm.replaceRange("",from,cur)
37 cm.replaceRange("",from,cur)
38 } else {
38 } else {
39 cm.deleteH(-1,"char")
39 cm.deleteH(-1,"char")
40 }
40 }
41 };
41 };
42
42
43
43
44 var IPython = (function (IPython) {
44 var IPython = (function (IPython) {
45 "use strict";
45 "use strict";
46
46
47 var utils = IPython.utils;
47 var utils = IPython.utils;
48 var key = IPython.utils.keycodes;
48 var key = IPython.utils.keycodes;
49
49
50 /**
50 /**
51 * A Cell conceived to write code.
51 * A Cell conceived to write code.
52 *
52 *
53 * The kernel doesn't have to be set at creation time, in that case
53 * The kernel doesn't have to be set at creation time, in that case
54 * it will be null and set_kernel has to be called later.
54 * it will be null and set_kernel has to be called later.
55 * @class CodeCell
55 * @class CodeCell
56 * @extends IPython.Cell
56 * @extends IPython.Cell
57 *
57 *
58 * @constructor
58 * @constructor
59 * @param {Object|null} kernel
59 * @param {Object|null} kernel
60 * @param {object|undefined} [options]
60 * @param {object|undefined} [options]
61 * @param [options.cm_config] {object} config to pass to CodeMirror
61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 */
62 */
63 var CodeCell = function (kernel, options) {
63 var CodeCell = function (kernel, options) {
64 this.kernel = kernel || null;
64 this.kernel = kernel || null;
65 this.code_mirror = null;
65 this.code_mirror = null;
66 this.input_prompt_number = null;
66 this.input_prompt_number = null;
67 this.collapsed = false;
67 this.collapsed = false;
68 this.default_mode = 'ipython';
68 this.default_mode = 'ipython';
69 this.cell_type = "code";
69 this.cell_type = "code";
70
70
71
71
72 var cm_overwrite_options = {
72 var cm_overwrite_options = {
73 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
73 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
74 };
74 };
75
75
76 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
76 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
77
77
78 IPython.Cell.apply(this,[options]);
78 IPython.Cell.apply(this,[options]);
79
79
80 var that = this;
80 var that = this;
81 this.element.focusout(
81 this.element.focusout(
82 function() { that.auto_highlight(); }
82 function() { that.auto_highlight(); }
83 );
83 );
84 };
84 };
85
85
86 CodeCell.options_default = {
86 CodeCell.options_default = {
87 cm_config : {
87 cm_config : {
88 extraKeys: {
88 extraKeys: {
89 "Tab" : "indentMore",
89 "Tab" : "indentMore",
90 "Shift-Tab" : "indentLess",
90 "Shift-Tab" : "indentLess",
91 "Backspace" : "delSpaceToPrevTabStop",
91 "Backspace" : "delSpaceToPrevTabStop",
92 "Cmd-/" : "toggleComment",
92 "Cmd-/" : "toggleComment",
93 "Ctrl-/" : "toggleComment"
93 "Ctrl-/" : "toggleComment"
94 },
94 },
95 mode: 'ipython',
95 mode: 'ipython',
96 theme: 'ipython',
96 theme: 'ipython',
97 matchBrackets: true
97 matchBrackets: true
98 }
98 }
99 };
99 };
100
100
101
101
102 CodeCell.prototype = new IPython.Cell();
102 CodeCell.prototype = new IPython.Cell();
103
103
104 /**
104 /**
105 * @method auto_highlight
105 * @method auto_highlight
106 */
106 */
107 CodeCell.prototype.auto_highlight = function () {
107 CodeCell.prototype.auto_highlight = function () {
108 this._auto_highlight(IPython.config.cell_magic_highlight)
108 this._auto_highlight(IPython.config.cell_magic_highlight)
109 };
109 };
110
110
111 /** @method create_element */
111 /** @method create_element */
112 CodeCell.prototype.create_element = function () {
112 CodeCell.prototype.create_element = function () {
113 IPython.Cell.prototype.create_element.apply(this, arguments);
113 IPython.Cell.prototype.create_element.apply(this, arguments);
114
114
115 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
115 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
116 cell.attr('tabindex','2');
116 cell.attr('tabindex','2');
117
117
118 this.celltoolbar = new IPython.CellToolbar(this);
118 this.celltoolbar = new IPython.CellToolbar(this);
119
119
120 var input = $('<div></div>').addClass('input');
120 var input = $('<div></div>').addClass('input');
121 var vbox = $('<div/>').addClass('vbox box-flex1')
121 var vbox = $('<div/>').addClass('vbox box-flex1')
122 input.append($('<div/>').addClass('prompt input_prompt'));
122 input.append($('<div/>').addClass('prompt input_prompt'));
123 vbox.append(this.celltoolbar.element);
123 vbox.append(this.celltoolbar.element);
124 var input_area = $('<div/>').addClass('input_area');
124 var input_area = $('<div/>').addClass('input_area');
125 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
125 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
126 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
126 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
127 vbox.append(input_area);
127 vbox.append(input_area);
128 input.append(vbox);
128 input.append(vbox);
129 var output = $('<div></div>');
129 var output = $('<div></div>');
130 cell.append(input).append(output);
130 cell.append(input).append(output);
131 this.element = cell;
131 this.element = cell;
132 this.output_area = new IPython.OutputArea(output, true);
132 this.output_area = new IPython.OutputArea(output, true);
133
133
134 // construct a completer only if class exist
134 // construct a completer only if class exist
135 // otherwise no print view
135 // otherwise no print view
136 if (IPython.Completer !== undefined)
136 if (IPython.Completer !== undefined)
137 {
137 {
138 this.completer = new IPython.Completer(this);
138 this.completer = new IPython.Completer(this);
139 }
139 }
140 };
140 };
141
141
142 /**
142 /**
143 * This method gets called in CodeMirror's onKeyDown/onKeyPress
143 * This method gets called in CodeMirror's onKeyDown/onKeyPress
144 * handlers and is used to provide custom key handling. Its return
144 * handlers and is used to provide custom key handling. Its return
145 * value is used to determine if CodeMirror should ignore the event:
145 * value is used to determine if CodeMirror should ignore the event:
146 * true = ignore, false = don't ignore.
146 * true = ignore, false = don't ignore.
147 * @method handle_codemirror_keyevent
147 * @method handle_codemirror_keyevent
148 */
148 */
149 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
149 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
150
150
151 if (this.read_only){
152 return false;
153 }
154
155 var that = this;
151 var that = this;
156 // whatever key is pressed, first, cancel the tooltip request before
152 // whatever key is pressed, first, cancel the tooltip request before
157 // they are sent, and remove tooltip if any, except for tab again
153 // they are sent, and remove tooltip if any, except for tab again
158 if (event.type === 'keydown' && event.which != key.TAB ) {
154 if (event.type === 'keydown' && event.which != key.TAB ) {
159 IPython.tooltip.remove_and_cancel_tooltip();
155 IPython.tooltip.remove_and_cancel_tooltip();
160 };
156 };
161
157
162 var cur = editor.getCursor();
158 var cur = editor.getCursor();
163 if (event.keyCode === key.ENTER){
159 if (event.keyCode === key.ENTER){
164 this.auto_highlight();
160 this.auto_highlight();
165 }
161 }
166
162
167 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
163 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
168 // Always ignore shift-enter in CodeMirror as we handle it.
164 // Always ignore shift-enter in CodeMirror as we handle it.
169 return true;
165 return true;
170 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
166 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
171 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
167 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
172 // browser and keyboard layout !
168 // browser and keyboard layout !
173 // Pressing '(' , request tooltip, don't forget to reappend it
169 // Pressing '(' , request tooltip, don't forget to reappend it
174 IPython.tooltip.pending(that);
170 IPython.tooltip.pending(that);
175 } else if (event.which === key.UPARROW && event.type === 'keydown') {
171 } else if (event.which === key.UPARROW && event.type === 'keydown') {
176 // If we are not at the top, let CM handle the up arrow and
172 // If we are not at the top, let CM handle the up arrow and
177 // prevent the global keydown handler from handling it.
173 // prevent the global keydown handler from handling it.
178 if (!that.at_top()) {
174 if (!that.at_top()) {
179 event.stop();
175 event.stop();
180 return false;
176 return false;
181 } else {
177 } else {
182 return true;
178 return true;
183 };
179 };
184 } else if (event.which === key.ESC) {
180 } else if (event.which === key.ESC) {
185 IPython.tooltip.remove_and_cancel_tooltip(true);
181 IPython.tooltip.remove_and_cancel_tooltip(true);
186 return true;
182 return true;
187 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
183 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
188 // If we are not at the bottom, let CM handle the down arrow and
184 // If we are not at the bottom, let CM handle the down arrow and
189 // prevent the global keydown handler from handling it.
185 // prevent the global keydown handler from handling it.
190 if (!that.at_bottom()) {
186 if (!that.at_bottom()) {
191 event.stop();
187 event.stop();
192 return false;
188 return false;
193 } else {
189 } else {
194 return true;
190 return true;
195 };
191 };
196 } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) {
192 } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) {
197 if (editor.somethingSelected()){
193 if (editor.somethingSelected()){
198 var anchor = editor.getCursor("anchor");
194 var anchor = editor.getCursor("anchor");
199 var head = editor.getCursor("head");
195 var head = editor.getCursor("head");
200 if( anchor.line != head.line){
196 if( anchor.line != head.line){
201 return false;
197 return false;
202 }
198 }
203 }
199 }
204 IPython.tooltip.request(that);
200 IPython.tooltip.request(that);
205 event.stop();
201 event.stop();
206 return true;
202 return true;
207 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
203 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
208 // Tab completion.
204 // Tab completion.
209 //Do not trim here because of tooltip
205 //Do not trim here because of tooltip
210 if (editor.somethingSelected()){return false}
206 if (editor.somethingSelected()){return false}
211 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
207 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
212 if (pre_cursor.trim() === "") {
208 if (pre_cursor.trim() === "") {
213 // Don't autocomplete if the part of the line before the cursor
209 // Don't autocomplete if the part of the line before the cursor
214 // is empty. In this case, let CodeMirror handle indentation.
210 // is empty. In this case, let CodeMirror handle indentation.
215 return false;
211 return false;
216 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && IPython.config.tooltip_on_tab ) {
212 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && IPython.config.tooltip_on_tab ) {
217 IPython.tooltip.request(that);
213 IPython.tooltip.request(that);
218 // Prevent the event from bubbling up.
214 // Prevent the event from bubbling up.
219 event.stop();
215 event.stop();
220 // Prevent CodeMirror from handling the tab.
216 // Prevent CodeMirror from handling the tab.
221 return true;
217 return true;
222 } else {
218 } else {
223 event.stop();
219 event.stop();
224 this.completer.startCompletion();
220 this.completer.startCompletion();
225 return true;
221 return true;
226 };
222 };
227 } else {
223 } else {
228 // keypress/keyup also trigger on TAB press, and we don't want to
224 // keypress/keyup also trigger on TAB press, and we don't want to
229 // use those to disable tab completion.
225 // use those to disable tab completion.
230 return false;
226 return false;
231 };
227 };
232 return false;
228 return false;
233 };
229 };
234
230
235
231
236 // Kernel related calls.
232 // Kernel related calls.
237
233
238 CodeCell.prototype.set_kernel = function (kernel) {
234 CodeCell.prototype.set_kernel = function (kernel) {
239 this.kernel = kernel;
235 this.kernel = kernel;
240 }
236 }
241
237
242 /**
238 /**
243 * Execute current code cell to the kernel
239 * Execute current code cell to the kernel
244 * @method execute
240 * @method execute
245 */
241 */
246 CodeCell.prototype.execute = function () {
242 CodeCell.prototype.execute = function () {
247 this.output_area.clear_output(true, true, true);
243 this.output_area.clear_output(true, true, true);
248 this.set_input_prompt('*');
244 this.set_input_prompt('*');
249 this.element.addClass("running");
245 this.element.addClass("running");
250 var callbacks = {
246 var callbacks = {
251 'execute_reply': $.proxy(this._handle_execute_reply, this),
247 'execute_reply': $.proxy(this._handle_execute_reply, this),
252 'output': $.proxy(this.output_area.handle_output, this.output_area),
248 'output': $.proxy(this.output_area.handle_output, this.output_area),
253 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
249 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
254 'set_next_input': $.proxy(this._handle_set_next_input, this),
250 'set_next_input': $.proxy(this._handle_set_next_input, this),
255 'input_request': $.proxy(this._handle_input_request, this)
251 'input_request': $.proxy(this._handle_input_request, this)
256 };
252 };
257 var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false});
253 var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false});
258 };
254 };
259
255
260 /**
256 /**
261 * @method _handle_execute_reply
257 * @method _handle_execute_reply
262 * @private
258 * @private
263 */
259 */
264 CodeCell.prototype._handle_execute_reply = function (content) {
260 CodeCell.prototype._handle_execute_reply = function (content) {
265 this.set_input_prompt(content.execution_count);
261 this.set_input_prompt(content.execution_count);
266 this.element.removeClass("running");
262 this.element.removeClass("running");
267 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
263 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
268 }
264 }
269
265
270 /**
266 /**
271 * @method _handle_set_next_input
267 * @method _handle_set_next_input
272 * @private
268 * @private
273 */
269 */
274 CodeCell.prototype._handle_set_next_input = function (text) {
270 CodeCell.prototype._handle_set_next_input = function (text) {
275 var data = {'cell': this, 'text': text}
271 var data = {'cell': this, 'text': text}
276 $([IPython.events]).trigger('set_next_input.Notebook', data);
272 $([IPython.events]).trigger('set_next_input.Notebook', data);
277 }
273 }
278
274
279 /**
275 /**
280 * @method _handle_input_request
276 * @method _handle_input_request
281 * @private
277 * @private
282 */
278 */
283 CodeCell.prototype._handle_input_request = function (content) {
279 CodeCell.prototype._handle_input_request = function (content) {
284 this.output_area.append_raw_input(content);
280 this.output_area.append_raw_input(content);
285 }
281 }
286
282
287
283
288 // Basic cell manipulation.
284 // Basic cell manipulation.
289
285
290 CodeCell.prototype.select = function () {
286 CodeCell.prototype.select = function () {
291 IPython.Cell.prototype.select.apply(this);
287 IPython.Cell.prototype.select.apply(this);
292 this.code_mirror.refresh();
288 this.code_mirror.refresh();
293 this.code_mirror.focus();
289 this.code_mirror.focus();
294 this.auto_highlight();
290 this.auto_highlight();
295 // We used to need an additional refresh() after the focus, but
291 // We used to need an additional refresh() after the focus, but
296 // it appears that this has been fixed in CM. This bug would show
292 // it appears that this has been fixed in CM. This bug would show
297 // up on FF when a newly loaded markdown cell was edited.
293 // up on FF when a newly loaded markdown cell was edited.
298 };
294 };
299
295
300
296
301 CodeCell.prototype.select_all = function () {
297 CodeCell.prototype.select_all = function () {
302 var start = {line: 0, ch: 0};
298 var start = {line: 0, ch: 0};
303 var nlines = this.code_mirror.lineCount();
299 var nlines = this.code_mirror.lineCount();
304 var last_line = this.code_mirror.getLine(nlines-1);
300 var last_line = this.code_mirror.getLine(nlines-1);
305 var end = {line: nlines-1, ch: last_line.length};
301 var end = {line: nlines-1, ch: last_line.length};
306 this.code_mirror.setSelection(start, end);
302 this.code_mirror.setSelection(start, end);
307 };
303 };
308
304
309
305
310 CodeCell.prototype.collapse = function () {
306 CodeCell.prototype.collapse = function () {
311 this.collapsed = true;
307 this.collapsed = true;
312 this.output_area.collapse();
308 this.output_area.collapse();
313 };
309 };
314
310
315
311
316 CodeCell.prototype.expand = function () {
312 CodeCell.prototype.expand = function () {
317 this.collapsed = false;
313 this.collapsed = false;
318 this.output_area.expand();
314 this.output_area.expand();
319 };
315 };
320
316
321
317
322 CodeCell.prototype.toggle_output = function () {
318 CodeCell.prototype.toggle_output = function () {
323 this.collapsed = Boolean(1 - this.collapsed);
319 this.collapsed = Boolean(1 - this.collapsed);
324 this.output_area.toggle_output();
320 this.output_area.toggle_output();
325 };
321 };
326
322
327
323
328 CodeCell.prototype.toggle_output_scroll = function () {
324 CodeCell.prototype.toggle_output_scroll = function () {
329 this.output_area.toggle_scroll();
325 this.output_area.toggle_scroll();
330 };
326 };
331
327
332
328
333 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
329 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
334 var ns = prompt_value || "&nbsp;";
330 var ns = prompt_value || "&nbsp;";
335 return 'In&nbsp;[' + ns + ']:'
331 return 'In&nbsp;[' + ns + ']:'
336 };
332 };
337
333
338 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
334 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
339 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
335 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
340 for(var i=1; i < lines_number; i++){html.push(['...:'])};
336 for(var i=1; i < lines_number; i++){html.push(['...:'])};
341 return html.join('</br>')
337 return html.join('</br>')
342 };
338 };
343
339
344 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
340 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
345
341
346
342
347 CodeCell.prototype.set_input_prompt = function (number) {
343 CodeCell.prototype.set_input_prompt = function (number) {
348 var nline = 1
344 var nline = 1
349 if( this.code_mirror != undefined) {
345 if( this.code_mirror != undefined) {
350 nline = this.code_mirror.lineCount();
346 nline = this.code_mirror.lineCount();
351 }
347 }
352 this.input_prompt_number = number;
348 this.input_prompt_number = number;
353 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
349 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
354 this.element.find('div.input_prompt').html(prompt_html);
350 this.element.find('div.input_prompt').html(prompt_html);
355 };
351 };
356
352
357
353
358 CodeCell.prototype.clear_input = function () {
354 CodeCell.prototype.clear_input = function () {
359 this.code_mirror.setValue('');
355 this.code_mirror.setValue('');
360 };
356 };
361
357
362
358
363 CodeCell.prototype.get_text = function () {
359 CodeCell.prototype.get_text = function () {
364 return this.code_mirror.getValue();
360 return this.code_mirror.getValue();
365 };
361 };
366
362
367
363
368 CodeCell.prototype.set_text = function (code) {
364 CodeCell.prototype.set_text = function (code) {
369 return this.code_mirror.setValue(code);
365 return this.code_mirror.setValue(code);
370 };
366 };
371
367
372
368
373 CodeCell.prototype.at_top = function () {
369 CodeCell.prototype.at_top = function () {
374 var cursor = this.code_mirror.getCursor();
370 var cursor = this.code_mirror.getCursor();
375 if (cursor.line === 0 && cursor.ch === 0) {
371 if (cursor.line === 0 && cursor.ch === 0) {
376 return true;
372 return true;
377 } else {
373 } else {
378 return false;
374 return false;
379 }
375 }
380 };
376 };
381
377
382
378
383 CodeCell.prototype.at_bottom = function () {
379 CodeCell.prototype.at_bottom = function () {
384 var cursor = this.code_mirror.getCursor();
380 var cursor = this.code_mirror.getCursor();
385 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
381 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
386 return true;
382 return true;
387 } else {
383 } else {
388 return false;
384 return false;
389 }
385 }
390 };
386 };
391
387
392
388
393 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
389 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
394 this.output_area.clear_output(stdout, stderr, other);
390 this.output_area.clear_output(stdout, stderr, other);
395 };
391 };
396
392
397
393
398 // JSON serialization
394 // JSON serialization
399
395
400 CodeCell.prototype.fromJSON = function (data) {
396 CodeCell.prototype.fromJSON = function (data) {
401 IPython.Cell.prototype.fromJSON.apply(this, arguments);
397 IPython.Cell.prototype.fromJSON.apply(this, arguments);
402 if (data.cell_type === 'code') {
398 if (data.cell_type === 'code') {
403 if (data.input !== undefined) {
399 if (data.input !== undefined) {
404 this.set_text(data.input);
400 this.set_text(data.input);
405 // make this value the starting point, so that we can only undo
401 // make this value the starting point, so that we can only undo
406 // to this state, instead of a blank cell
402 // to this state, instead of a blank cell
407 this.code_mirror.clearHistory();
403 this.code_mirror.clearHistory();
408 this.auto_highlight();
404 this.auto_highlight();
409 }
405 }
410 if (data.prompt_number !== undefined) {
406 if (data.prompt_number !== undefined) {
411 this.set_input_prompt(data.prompt_number);
407 this.set_input_prompt(data.prompt_number);
412 } else {
408 } else {
413 this.set_input_prompt();
409 this.set_input_prompt();
414 };
410 };
415 this.output_area.fromJSON(data.outputs);
411 this.output_area.fromJSON(data.outputs);
416 if (data.collapsed !== undefined) {
412 if (data.collapsed !== undefined) {
417 if (data.collapsed) {
413 if (data.collapsed) {
418 this.collapse();
414 this.collapse();
419 } else {
415 } else {
420 this.expand();
416 this.expand();
421 };
417 };
422 };
418 };
423 };
419 };
424 };
420 };
425
421
426
422
427 CodeCell.prototype.toJSON = function () {
423 CodeCell.prototype.toJSON = function () {
428 var data = IPython.Cell.prototype.toJSON.apply(this);
424 var data = IPython.Cell.prototype.toJSON.apply(this);
429 data.input = this.get_text();
425 data.input = this.get_text();
430 data.cell_type = 'code';
426 data.cell_type = 'code';
431 if (this.input_prompt_number) {
427 if (this.input_prompt_number) {
432 data.prompt_number = this.input_prompt_number;
428 data.prompt_number = this.input_prompt_number;
433 };
429 };
434 var outputs = this.output_area.toJSON();
430 var outputs = this.output_area.toJSON();
435 data.outputs = outputs;
431 data.outputs = outputs;
436 data.language = 'python';
432 data.language = 'python';
437 data.collapsed = this.collapsed;
433 data.collapsed = this.collapsed;
438 return data;
434 return data;
439 };
435 };
440
436
441
437
442 IPython.CodeCell = CodeCell;
438 IPython.CodeCell = CodeCell;
443
439
444 return IPython;
440 return IPython;
445 }(IPython));
441 }(IPython));
@@ -1,124 +1,114 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // On document ready
9 // On document ready
10 //============================================================================
10 //============================================================================
11 "use strict";
11 "use strict";
12
12
13 // for the time beeing, we have to pass marked as a parameter here,
13 // for the time beeing, we have to pass marked as a parameter here,
14 // as injecting require.js make marked not to put itself in the globals,
14 // as injecting require.js make marked not to put itself in the globals,
15 // which make both this file fail at setting marked configuration, and textcell.js
15 // which make both this file fail at setting marked configuration, and textcell.js
16 // which search marked into global.
16 // which search marked into global.
17 require(['components/marked/lib/marked'],
17 require(['components/marked/lib/marked'],
18
18
19 function (marked) {
19 function (marked) {
20
20
21 window.marked = marked
21 window.marked = marked
22
22
23 // monkey patch CM to be able to syntax highlight cell magics
23 // monkey patch CM to be able to syntax highlight cell magics
24 // bug reported upstream,
24 // bug reported upstream,
25 // see https://github.com/marijnh/CodeMirror2/issues/670
25 // see https://github.com/marijnh/CodeMirror2/issues/670
26 if(CodeMirror.getMode(1,'text/plain').indent == undefined ){
26 if(CodeMirror.getMode(1,'text/plain').indent == undefined ){
27 console.log('patching CM for undefined indent');
27 console.log('patching CM for undefined indent');
28 CodeMirror.modes.null = function() {
28 CodeMirror.modes.null = function() {
29 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0}}
29 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0}}
30 }
30 }
31 }
31 }
32
32
33 CodeMirror.patchedGetMode = function(config, mode){
33 CodeMirror.patchedGetMode = function(config, mode){
34 var cmmode = CodeMirror.getMode(config, mode);
34 var cmmode = CodeMirror.getMode(config, mode);
35 if(cmmode.indent == null)
35 if(cmmode.indent == null)
36 {
36 {
37 console.log('patch mode "' , mode, '" on the fly');
37 console.log('patch mode "' , mode, '" on the fly');
38 cmmode.indent = function(){return 0};
38 cmmode.indent = function(){return 0};
39 }
39 }
40 return cmmode;
40 return cmmode;
41 }
41 }
42 // end monkey patching CodeMirror
42 // end monkey patching CodeMirror
43
43
44 IPython.mathjaxutils.init();
44 IPython.mathjaxutils.init();
45
45
46 IPython.read_only = $('body').data('readOnly') === 'True';
47 $('#ipython-main-app').addClass('border-box-sizing');
46 $('#ipython-main-app').addClass('border-box-sizing');
48 $('div#notebook_panel').addClass('border-box-sizing');
47 $('div#notebook_panel').addClass('border-box-sizing');
49
48
50 var baseProjectUrl = $('body').data('baseProjectUrl')
49 var baseProjectUrl = $('body').data('baseProjectUrl')
51
50
52 IPython.page = new IPython.Page();
51 IPython.page = new IPython.Page();
53 IPython.layout_manager = new IPython.LayoutManager();
52 IPython.layout_manager = new IPython.LayoutManager();
54 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
53 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
55 IPython.quick_help = new IPython.QuickHelp();
54 IPython.quick_help = new IPython.QuickHelp();
56 IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl});
55 IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl});
57 IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, read_only:IPython.read_only});
56 IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl});
58 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
57 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
59 IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl})
58 IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl})
60 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container')
59 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container')
61 IPython.tooltip = new IPython.Tooltip()
60 IPython.tooltip = new IPython.Tooltip()
62 IPython.notification_area = new IPython.NotificationArea('#notification_area')
61 IPython.notification_area = new IPython.NotificationArea('#notification_area')
63 IPython.notification_area.init_notification_widgets();
62 IPython.notification_area.init_notification_widgets();
64
63
65 IPython.layout_manager.do_resize();
64 IPython.layout_manager.do_resize();
66
65
67 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
66 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
68 '<span id="test2" style="font-weight: bold;">x</span>'+
67 '<span id="test2" style="font-weight: bold;">x</span>'+
69 '<span id="test3" style="font-style: italic;">x</span></pre></div>')
68 '<span id="test3" style="font-style: italic;">x</span></pre></div>')
70 var nh = $('#test1').innerHeight();
69 var nh = $('#test1').innerHeight();
71 var bh = $('#test2').innerHeight();
70 var bh = $('#test2').innerHeight();
72 var ih = $('#test3').innerHeight();
71 var ih = $('#test3').innerHeight();
73 if(nh != bh || nh != ih) {
72 if(nh != bh || nh != ih) {
74 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
73 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
75 }
74 }
76 $('#fonttest').remove();
75 $('#fonttest').remove();
77
76
78 if(IPython.read_only){
79 // hide various elements from read-only view
80 $('div#pager').remove();
81 $('div#pager_splitter').remove();
82
83 // set the notebook name field as not modifiable
84 $('#notebook_name').attr('disabled','disabled')
85 }
86
87 IPython.page.show();
77 IPython.page.show();
88
78
89 IPython.layout_manager.do_resize();
79 IPython.layout_manager.do_resize();
90 var first_load = function () {
80 var first_load = function () {
91 IPython.layout_manager.do_resize();
81 IPython.layout_manager.do_resize();
92 var hash = document.location.hash;
82 var hash = document.location.hash;
93 if (hash) {
83 if (hash) {
94 document.location.hash = '';
84 document.location.hash = '';
95 document.location.hash = hash;
85 document.location.hash = hash;
96 }
86 }
97 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
87 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
98 // only do this once
88 // only do this once
99 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
89 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
100 };
90 };
101
91
102 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
92 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
103 $([IPython.events]).trigger('app_initialized.NotebookApp');
93 $([IPython.events]).trigger('app_initialized.NotebookApp');
104 IPython.notebook.load_notebook($('body').data('notebookId'));
94 IPython.notebook.load_notebook($('body').data('notebookId'));
105
95
106 if (marked) {
96 if (marked) {
107 marked.setOptions({
97 marked.setOptions({
108 gfm : true,
98 gfm : true,
109 tables: true,
99 tables: true,
110 langPrefix: "language-",
100 langPrefix: "language-",
111 highlight: function(code, lang) {
101 highlight: function(code, lang) {
112 var highlighted;
102 var highlighted;
113 try {
103 try {
114 highlighted = hljs.highlight(lang, code, false);
104 highlighted = hljs.highlight(lang, code, false);
115 } catch(err) {
105 } catch(err) {
116 highlighted = hljs.highlightAuto(code);
106 highlighted = hljs.highlightAuto(code);
117 }
107 }
118 return highlighted.value;
108 return highlighted.value;
119 }
109 }
120 })
110 })
121 }
111 }
122 }
112 }
123
113
124 );
114 );
@@ -1,2045 +1,2040 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15 var key = IPython.utils.keycodes;
15 var key = IPython.utils.keycodes;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 var options = options || {};
26 var options = options || {};
27 this._baseProjectUrl = options.baseProjectUrl;
27 this._baseProjectUrl = options.baseProjectUrl;
28 this.read_only = options.read_only || IPython.read_only;
29
28
30 this.element = $(selector);
29 this.element = $(selector);
31 this.element.scroll();
30 this.element.scroll();
32 this.element.data("notebook", this);
31 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
32 this.next_prompt_number = 1;
34 this.kernel = null;
33 this.kernel = null;
35 this.clipboard = null;
34 this.clipboard = null;
36 this.undelete_backup = null;
35 this.undelete_backup = null;
37 this.undelete_index = null;
36 this.undelete_index = null;
38 this.undelete_below = false;
37 this.undelete_below = false;
39 this.paste_enabled = false;
38 this.paste_enabled = false;
40 this.set_dirty(false);
39 this.set_dirty(false);
41 this.metadata = {};
40 this.metadata = {};
42 this._checkpoint_after_save = false;
41 this._checkpoint_after_save = false;
43 this.last_checkpoint = null;
42 this.last_checkpoint = null;
44 this.autosave_interval = 0;
43 this.autosave_interval = 0;
45 this.autosave_timer = null;
44 this.autosave_timer = null;
46 // autosave *at most* every two minutes
45 // autosave *at most* every two minutes
47 this.minimum_autosave_interval = 120000;
46 this.minimum_autosave_interval = 120000;
48 // single worksheet for now
47 // single worksheet for now
49 this.worksheet_metadata = {};
48 this.worksheet_metadata = {};
50 this.control_key_active = false;
49 this.control_key_active = false;
51 this.notebook_id = null;
50 this.notebook_id = null;
52 this.notebook_name = null;
51 this.notebook_name = null;
53 this.notebook_name_blacklist_re = /[\/\\:]/;
52 this.notebook_name_blacklist_re = /[\/\\:]/;
54 this.nbformat = 3 // Increment this when changing the nbformat
53 this.nbformat = 3 // Increment this when changing the nbformat
55 this.nbformat_minor = 0 // Increment this when changing the nbformat
54 this.nbformat_minor = 0 // Increment this when changing the nbformat
56 this.style();
55 this.style();
57 this.create_elements();
56 this.create_elements();
58 this.bind_events();
57 this.bind_events();
59 };
58 };
60
59
61 /**
60 /**
62 * Tweak the notebook's CSS style.
61 * Tweak the notebook's CSS style.
63 *
62 *
64 * @method style
63 * @method style
65 */
64 */
66 Notebook.prototype.style = function () {
65 Notebook.prototype.style = function () {
67 $('div#notebook').addClass('border-box-sizing');
66 $('div#notebook').addClass('border-box-sizing');
68 };
67 };
69
68
70 /**
69 /**
71 * Get the root URL of the notebook server.
70 * Get the root URL of the notebook server.
72 *
71 *
73 * @method baseProjectUrl
72 * @method baseProjectUrl
74 * @return {String} The base project URL
73 * @return {String} The base project URL
75 */
74 */
76 Notebook.prototype.baseProjectUrl = function(){
75 Notebook.prototype.baseProjectUrl = function(){
77 return this._baseProjectUrl || $('body').data('baseProjectUrl');
76 return this._baseProjectUrl || $('body').data('baseProjectUrl');
78 };
77 };
79
78
80 /**
79 /**
81 * Create an HTML and CSS representation of the notebook.
80 * Create an HTML and CSS representation of the notebook.
82 *
81 *
83 * @method create_elements
82 * @method create_elements
84 */
83 */
85 Notebook.prototype.create_elements = function () {
84 Notebook.prototype.create_elements = function () {
86 // We add this end_space div to the end of the notebook div to:
85 // We add this end_space div to the end of the notebook div to:
87 // i) provide a margin between the last cell and the end of the notebook
86 // i) provide a margin between the last cell and the end of the notebook
88 // ii) to prevent the div from scrolling up when the last cell is being
87 // ii) to prevent the div from scrolling up when the last cell is being
89 // edited, but is too low on the page, which browsers will do automatically.
88 // edited, but is too low on the page, which browsers will do automatically.
90 var that = this;
89 var that = this;
91 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
90 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
92 var end_space = $('<div/>').addClass('end_space');
91 var end_space = $('<div/>').addClass('end_space');
93 end_space.dblclick(function (e) {
92 end_space.dblclick(function (e) {
94 if (that.read_only) return;
95 var ncells = that.ncells();
93 var ncells = that.ncells();
96 that.insert_cell_below('code',ncells-1);
94 that.insert_cell_below('code',ncells-1);
97 });
95 });
98 this.element.append(this.container);
96 this.element.append(this.container);
99 this.container.append(end_space);
97 this.container.append(end_space);
100 $('div#notebook').addClass('border-box-sizing');
98 $('div#notebook').addClass('border-box-sizing');
101 };
99 };
102
100
103 /**
101 /**
104 * Bind JavaScript events: key presses and custom IPython events.
102 * Bind JavaScript events: key presses and custom IPython events.
105 *
103 *
106 * @method bind_events
104 * @method bind_events
107 */
105 */
108 Notebook.prototype.bind_events = function () {
106 Notebook.prototype.bind_events = function () {
109 var that = this;
107 var that = this;
110
108
111 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
109 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
112 var index = that.find_cell_index(data.cell);
110 var index = that.find_cell_index(data.cell);
113 var new_cell = that.insert_cell_below('code',index);
111 var new_cell = that.insert_cell_below('code',index);
114 new_cell.set_text(data.text);
112 new_cell.set_text(data.text);
115 that.dirty = true;
113 that.dirty = true;
116 });
114 });
117
115
118 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
116 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
119 that.dirty = data.value;
117 that.dirty = data.value;
120 });
118 });
121
119
122 $([IPython.events]).on('select.Cell', function (event, data) {
120 $([IPython.events]).on('select.Cell', function (event, data) {
123 var index = that.find_cell_index(data.cell);
121 var index = that.find_cell_index(data.cell);
124 that.select(index);
122 that.select(index);
125 });
123 });
126
124
127 $([IPython.events]).on('status_autorestarting.Kernel', function () {
125 $([IPython.events]).on('status_autorestarting.Kernel', function () {
128 IPython.dialog.modal({
126 IPython.dialog.modal({
129 title: "Kernel Restarting",
127 title: "Kernel Restarting",
130 body: "The kernel appears to have died. It will restart automatically.",
128 body: "The kernel appears to have died. It will restart automatically.",
131 buttons: {
129 buttons: {
132 OK : {
130 OK : {
133 class : "btn-primary"
131 class : "btn-primary"
134 }
132 }
135 }
133 }
136 });
134 });
137 });
135 });
138
136
139
137
140 $(document).keydown(function (event) {
138 $(document).keydown(function (event) {
141 // console.log(event);
142 if (that.read_only) return true;
143
139
144 // Save (CTRL+S) or (AppleKey+S)
140 // Save (CTRL+S) or (AppleKey+S)
145 //metaKey = applekey on mac
141 //metaKey = applekey on mac
146 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
142 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
147 that.save_checkpoint();
143 that.save_checkpoint();
148 event.preventDefault();
144 event.preventDefault();
149 return false;
145 return false;
150 } else if (event.which === key.ESC) {
146 } else if (event.which === key.ESC) {
151 // Intercept escape at highest level to avoid closing
147 // Intercept escape at highest level to avoid closing
152 // websocket connection with firefox
148 // websocket connection with firefox
153 IPython.pager.collapse();
149 IPython.pager.collapse();
154 event.preventDefault();
150 event.preventDefault();
155 } else if (event.which === key.SHIFT) {
151 } else if (event.which === key.SHIFT) {
156 // ignore shift keydown
152 // ignore shift keydown
157 return true;
153 return true;
158 }
154 }
159 if (event.which === key.UPARROW && !event.shiftKey) {
155 if (event.which === key.UPARROW && !event.shiftKey) {
160 var cell = that.get_selected_cell();
156 var cell = that.get_selected_cell();
161 if (cell && cell.at_top()) {
157 if (cell && cell.at_top()) {
162 event.preventDefault();
158 event.preventDefault();
163 that.select_prev();
159 that.select_prev();
164 };
160 };
165 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
161 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
166 var cell = that.get_selected_cell();
162 var cell = that.get_selected_cell();
167 if (cell && cell.at_bottom()) {
163 if (cell && cell.at_bottom()) {
168 event.preventDefault();
164 event.preventDefault();
169 that.select_next();
165 that.select_next();
170 };
166 };
171 } else if (event.which === key.ENTER && event.shiftKey) {
167 } else if (event.which === key.ENTER && event.shiftKey) {
172 that.execute_selected_cell();
168 that.execute_selected_cell();
173 return false;
169 return false;
174 } else if (event.which === key.ENTER && event.altKey) {
170 } else if (event.which === key.ENTER && event.altKey) {
175 // Execute code cell, and insert new in place
171 // Execute code cell, and insert new in place
176 that.execute_selected_cell();
172 that.execute_selected_cell();
177 // Only insert a new cell, if we ended up in an already populated cell
173 // Only insert a new cell, if we ended up in an already populated cell
178 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
174 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
179 that.insert_cell_above('code');
175 that.insert_cell_above('code');
180 }
176 }
181 return false;
177 return false;
182 } else if (event.which === key.ENTER && event.ctrlKey) {
178 } else if (event.which === key.ENTER && event.ctrlKey) {
183 that.execute_selected_cell({terminal:true});
179 that.execute_selected_cell({terminal:true});
184 return false;
180 return false;
185 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
181 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
186 that.control_key_active = true;
182 that.control_key_active = true;
187 return false;
183 return false;
188 } else if (event.which === 88 && that.control_key_active) {
184 } else if (event.which === 88 && that.control_key_active) {
189 // Cut selected cell = x
185 // Cut selected cell = x
190 that.cut_cell();
186 that.cut_cell();
191 that.control_key_active = false;
187 that.control_key_active = false;
192 return false;
188 return false;
193 } else if (event.which === 67 && that.control_key_active) {
189 } else if (event.which === 67 && that.control_key_active) {
194 // Copy selected cell = c
190 // Copy selected cell = c
195 that.copy_cell();
191 that.copy_cell();
196 that.control_key_active = false;
192 that.control_key_active = false;
197 return false;
193 return false;
198 } else if (event.which === 86 && that.control_key_active) {
194 } else if (event.which === 86 && that.control_key_active) {
199 // Paste below selected cell = v
195 // Paste below selected cell = v
200 that.paste_cell_below();
196 that.paste_cell_below();
201 that.control_key_active = false;
197 that.control_key_active = false;
202 return false;
198 return false;
203 } else if (event.which === 68 && that.control_key_active) {
199 } else if (event.which === 68 && that.control_key_active) {
204 // Delete selected cell = d
200 // Delete selected cell = d
205 that.delete_cell();
201 that.delete_cell();
206 that.control_key_active = false;
202 that.control_key_active = false;
207 return false;
203 return false;
208 } else if (event.which === 65 && that.control_key_active) {
204 } else if (event.which === 65 && that.control_key_active) {
209 // Insert code cell above selected = a
205 // Insert code cell above selected = a
210 that.insert_cell_above('code');
206 that.insert_cell_above('code');
211 that.control_key_active = false;
207 that.control_key_active = false;
212 return false;
208 return false;
213 } else if (event.which === 66 && that.control_key_active) {
209 } else if (event.which === 66 && that.control_key_active) {
214 // Insert code cell below selected = b
210 // Insert code cell below selected = b
215 that.insert_cell_below('code');
211 that.insert_cell_below('code');
216 that.control_key_active = false;
212 that.control_key_active = false;
217 return false;
213 return false;
218 } else if (event.which === 89 && that.control_key_active) {
214 } else if (event.which === 89 && that.control_key_active) {
219 // To code = y
215 // To code = y
220 that.to_code();
216 that.to_code();
221 that.control_key_active = false;
217 that.control_key_active = false;
222 return false;
218 return false;
223 } else if (event.which === 77 && that.control_key_active) {
219 } else if (event.which === 77 && that.control_key_active) {
224 // To markdown = m
220 // To markdown = m
225 that.to_markdown();
221 that.to_markdown();
226 that.control_key_active = false;
222 that.control_key_active = false;
227 return false;
223 return false;
228 } else if (event.which === 84 && that.control_key_active) {
224 } else if (event.which === 84 && that.control_key_active) {
229 // To Raw = t
225 // To Raw = t
230 that.to_raw();
226 that.to_raw();
231 that.control_key_active = false;
227 that.control_key_active = false;
232 return false;
228 return false;
233 } else if (event.which === 49 && that.control_key_active) {
229 } else if (event.which === 49 && that.control_key_active) {
234 // To Heading 1 = 1
230 // To Heading 1 = 1
235 that.to_heading(undefined, 1);
231 that.to_heading(undefined, 1);
236 that.control_key_active = false;
232 that.control_key_active = false;
237 return false;
233 return false;
238 } else if (event.which === 50 && that.control_key_active) {
234 } else if (event.which === 50 && that.control_key_active) {
239 // To Heading 2 = 2
235 // To Heading 2 = 2
240 that.to_heading(undefined, 2);
236 that.to_heading(undefined, 2);
241 that.control_key_active = false;
237 that.control_key_active = false;
242 return false;
238 return false;
243 } else if (event.which === 51 && that.control_key_active) {
239 } else if (event.which === 51 && that.control_key_active) {
244 // To Heading 3 = 3
240 // To Heading 3 = 3
245 that.to_heading(undefined, 3);
241 that.to_heading(undefined, 3);
246 that.control_key_active = false;
242 that.control_key_active = false;
247 return false;
243 return false;
248 } else if (event.which === 52 && that.control_key_active) {
244 } else if (event.which === 52 && that.control_key_active) {
249 // To Heading 4 = 4
245 // To Heading 4 = 4
250 that.to_heading(undefined, 4);
246 that.to_heading(undefined, 4);
251 that.control_key_active = false;
247 that.control_key_active = false;
252 return false;
248 return false;
253 } else if (event.which === 53 && that.control_key_active) {
249 } else if (event.which === 53 && that.control_key_active) {
254 // To Heading 5 = 5
250 // To Heading 5 = 5
255 that.to_heading(undefined, 5);
251 that.to_heading(undefined, 5);
256 that.control_key_active = false;
252 that.control_key_active = false;
257 return false;
253 return false;
258 } else if (event.which === 54 && that.control_key_active) {
254 } else if (event.which === 54 && that.control_key_active) {
259 // To Heading 6 = 6
255 // To Heading 6 = 6
260 that.to_heading(undefined, 6);
256 that.to_heading(undefined, 6);
261 that.control_key_active = false;
257 that.control_key_active = false;
262 return false;
258 return false;
263 } else if (event.which === 79 && that.control_key_active) {
259 } else if (event.which === 79 && that.control_key_active) {
264 // Toggle output = o
260 // Toggle output = o
265 if (event.shiftKey){
261 if (event.shiftKey){
266 that.toggle_output_scroll();
262 that.toggle_output_scroll();
267 } else {
263 } else {
268 that.toggle_output();
264 that.toggle_output();
269 }
265 }
270 that.control_key_active = false;
266 that.control_key_active = false;
271 return false;
267 return false;
272 } else if (event.which === 83 && that.control_key_active) {
268 } else if (event.which === 83 && that.control_key_active) {
273 // Save notebook = s
269 // Save notebook = s
274 that.save_checkpoint();
270 that.save_checkpoint();
275 that.control_key_active = false;
271 that.control_key_active = false;
276 return false;
272 return false;
277 } else if (event.which === 74 && that.control_key_active) {
273 } else if (event.which === 74 && that.control_key_active) {
278 // Move cell down = j
274 // Move cell down = j
279 that.move_cell_down();
275 that.move_cell_down();
280 that.control_key_active = false;
276 that.control_key_active = false;
281 return false;
277 return false;
282 } else if (event.which === 75 && that.control_key_active) {
278 } else if (event.which === 75 && that.control_key_active) {
283 // Move cell up = k
279 // Move cell up = k
284 that.move_cell_up();
280 that.move_cell_up();
285 that.control_key_active = false;
281 that.control_key_active = false;
286 return false;
282 return false;
287 } else if (event.which === 80 && that.control_key_active) {
283 } else if (event.which === 80 && that.control_key_active) {
288 // Select previous = p
284 // Select previous = p
289 that.select_prev();
285 that.select_prev();
290 that.control_key_active = false;
286 that.control_key_active = false;
291 return false;
287 return false;
292 } else if (event.which === 78 && that.control_key_active) {
288 } else if (event.which === 78 && that.control_key_active) {
293 // Select next = n
289 // Select next = n
294 that.select_next();
290 that.select_next();
295 that.control_key_active = false;
291 that.control_key_active = false;
296 return false;
292 return false;
297 } else if (event.which === 76 && that.control_key_active) {
293 } else if (event.which === 76 && that.control_key_active) {
298 // Toggle line numbers = l
294 // Toggle line numbers = l
299 that.cell_toggle_line_numbers();
295 that.cell_toggle_line_numbers();
300 that.control_key_active = false;
296 that.control_key_active = false;
301 return false;
297 return false;
302 } else if (event.which === 73 && that.control_key_active) {
298 } else if (event.which === 73 && that.control_key_active) {
303 // Interrupt kernel = i
299 // Interrupt kernel = i
304 that.kernel.interrupt();
300 that.kernel.interrupt();
305 that.control_key_active = false;
301 that.control_key_active = false;
306 return false;
302 return false;
307 } else if (event.which === 190 && that.control_key_active) {
303 } else if (event.which === 190 && that.control_key_active) {
308 // Restart kernel = . # matches qt console
304 // Restart kernel = . # matches qt console
309 that.restart_kernel();
305 that.restart_kernel();
310 that.control_key_active = false;
306 that.control_key_active = false;
311 return false;
307 return false;
312 } else if (event.which === 72 && that.control_key_active) {
308 } else if (event.which === 72 && that.control_key_active) {
313 // Show keyboard shortcuts = h
309 // Show keyboard shortcuts = h
314 IPython.quick_help.show_keyboard_shortcuts();
310 IPython.quick_help.show_keyboard_shortcuts();
315 that.control_key_active = false;
311 that.control_key_active = false;
316 return false;
312 return false;
317 } else if (event.which === 90 && that.control_key_active) {
313 } else if (event.which === 90 && that.control_key_active) {
318 // Undo last cell delete = z
314 // Undo last cell delete = z
319 that.undelete();
315 that.undelete();
320 that.control_key_active = false;
316 that.control_key_active = false;
321 return false;
317 return false;
322 } else if (event.which === 189 && that.control_key_active) {
318 } else if (event.which === 189 && that.control_key_active) {
323 // Split cell = -
319 // Split cell = -
324 that.split_cell();
320 that.split_cell();
325 that.control_key_active = false;
321 that.control_key_active = false;
326 return false;
322 return false;
327 } else if (that.control_key_active) {
323 } else if (that.control_key_active) {
328 that.control_key_active = false;
324 that.control_key_active = false;
329 return true;
325 return true;
330 }
326 }
331 return true;
327 return true;
332 });
328 });
333
329
334 var collapse_time = function(time){
330 var collapse_time = function(time){
335 var app_height = $('#ipython-main-app').height(); // content height
331 var app_height = $('#ipython-main-app').height(); // content height
336 var splitter_height = $('div#pager_splitter').outerHeight(true);
332 var splitter_height = $('div#pager_splitter').outerHeight(true);
337 var new_height = app_height - splitter_height;
333 var new_height = app_height - splitter_height;
338 that.element.animate({height : new_height + 'px'}, time);
334 that.element.animate({height : new_height + 'px'}, time);
339 }
335 }
340
336
341 this.element.bind('collapse_pager', function (event,extrap) {
337 this.element.bind('collapse_pager', function (event,extrap) {
342 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
338 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
343 collapse_time(time);
339 collapse_time(time);
344 });
340 });
345
341
346 var expand_time = function(time) {
342 var expand_time = function(time) {
347 var app_height = $('#ipython-main-app').height(); // content height
343 var app_height = $('#ipython-main-app').height(); // content height
348 var splitter_height = $('div#pager_splitter').outerHeight(true);
344 var splitter_height = $('div#pager_splitter').outerHeight(true);
349 var pager_height = $('div#pager').outerHeight(true);
345 var pager_height = $('div#pager').outerHeight(true);
350 var new_height = app_height - pager_height - splitter_height;
346 var new_height = app_height - pager_height - splitter_height;
351 that.element.animate({height : new_height + 'px'}, time);
347 that.element.animate({height : new_height + 'px'}, time);
352 }
348 }
353
349
354 this.element.bind('expand_pager', function (event, extrap) {
350 this.element.bind('expand_pager', function (event, extrap) {
355 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
351 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
356 expand_time(time);
352 expand_time(time);
357 });
353 });
358
354
359 // Firefox 22 broke $(window).on("beforeunload")
355 // Firefox 22 broke $(window).on("beforeunload")
360 // I'm not sure why or how.
356 // I'm not sure why or how.
361 window.onbeforeunload = function (e) {
357 window.onbeforeunload = function (e) {
362 // TODO: Make killing the kernel configurable.
358 // TODO: Make killing the kernel configurable.
363 var kill_kernel = false;
359 var kill_kernel = false;
364 if (kill_kernel) {
360 if (kill_kernel) {
365 that.kernel.kill();
361 that.kernel.kill();
366 }
362 }
367 // if we are autosaving, trigger an autosave on nav-away.
363 // if we are autosaving, trigger an autosave on nav-away.
368 // still warn, because if we don't the autosave may fail.
364 // still warn, because if we don't the autosave may fail.
369 if (that.dirty && ! that.read_only) {
365 if (that.dirty) {
370 if ( that.autosave_interval ) {
366 if ( that.autosave_interval ) {
371 // schedule autosave in a timeout
367 // schedule autosave in a timeout
372 // this gives you a chance to forcefully discard changes
368 // this gives you a chance to forcefully discard changes
373 // by reloading the page if you *really* want to.
369 // by reloading the page if you *really* want to.
374 // the timer doesn't start until you *dismiss* the dialog.
370 // the timer doesn't start until you *dismiss* the dialog.
375 setTimeout(function () {
371 setTimeout(function () {
376 if (that.dirty) {
372 if (that.dirty) {
377 that.save_notebook();
373 that.save_notebook();
378 }
374 }
379 }, 1000);
375 }, 1000);
380 return "Autosave in progress, latest changes may be lost.";
376 return "Autosave in progress, latest changes may be lost.";
381 } else {
377 } else {
382 return "Unsaved changes will be lost.";
378 return "Unsaved changes will be lost.";
383 }
379 }
384 };
380 };
385 // Null is the *only* return value that will make the browser not
381 // Null is the *only* return value that will make the browser not
386 // pop up the "don't leave" dialog.
382 // pop up the "don't leave" dialog.
387 return null;
383 return null;
388 };
384 };
389 };
385 };
390
386
391 /**
387 /**
392 * Set the dirty flag, and trigger the set_dirty.Notebook event
388 * Set the dirty flag, and trigger the set_dirty.Notebook event
393 *
389 *
394 * @method set_dirty
390 * @method set_dirty
395 */
391 */
396 Notebook.prototype.set_dirty = function (value) {
392 Notebook.prototype.set_dirty = function (value) {
397 if (value === undefined) {
393 if (value === undefined) {
398 value = true;
394 value = true;
399 }
395 }
400 if (this.dirty == value) {
396 if (this.dirty == value) {
401 return;
397 return;
402 }
398 }
403 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
399 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
404 };
400 };
405
401
406 /**
402 /**
407 * Scroll the top of the page to a given cell.
403 * Scroll the top of the page to a given cell.
408 *
404 *
409 * @method scroll_to_cell
405 * @method scroll_to_cell
410 * @param {Number} cell_number An index of the cell to view
406 * @param {Number} cell_number An index of the cell to view
411 * @param {Number} time Animation time in milliseconds
407 * @param {Number} time Animation time in milliseconds
412 * @return {Number} Pixel offset from the top of the container
408 * @return {Number} Pixel offset from the top of the container
413 */
409 */
414 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
410 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
415 var cells = this.get_cells();
411 var cells = this.get_cells();
416 var time = time || 0;
412 var time = time || 0;
417 cell_number = Math.min(cells.length-1,cell_number);
413 cell_number = Math.min(cells.length-1,cell_number);
418 cell_number = Math.max(0 ,cell_number);
414 cell_number = Math.max(0 ,cell_number);
419 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
415 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
420 this.element.animate({scrollTop:scroll_value}, time);
416 this.element.animate({scrollTop:scroll_value}, time);
421 return scroll_value;
417 return scroll_value;
422 };
418 };
423
419
424 /**
420 /**
425 * Scroll to the bottom of the page.
421 * Scroll to the bottom of the page.
426 *
422 *
427 * @method scroll_to_bottom
423 * @method scroll_to_bottom
428 */
424 */
429 Notebook.prototype.scroll_to_bottom = function () {
425 Notebook.prototype.scroll_to_bottom = function () {
430 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
426 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
431 };
427 };
432
428
433 /**
429 /**
434 * Scroll to the top of the page.
430 * Scroll to the top of the page.
435 *
431 *
436 * @method scroll_to_top
432 * @method scroll_to_top
437 */
433 */
438 Notebook.prototype.scroll_to_top = function () {
434 Notebook.prototype.scroll_to_top = function () {
439 this.element.animate({scrollTop:0}, 0);
435 this.element.animate({scrollTop:0}, 0);
440 };
436 };
441
437
442
438
443 // Cell indexing, retrieval, etc.
439 // Cell indexing, retrieval, etc.
444
440
445 /**
441 /**
446 * Get all cell elements in the notebook.
442 * Get all cell elements in the notebook.
447 *
443 *
448 * @method get_cell_elements
444 * @method get_cell_elements
449 * @return {jQuery} A selector of all cell elements
445 * @return {jQuery} A selector of all cell elements
450 */
446 */
451 Notebook.prototype.get_cell_elements = function () {
447 Notebook.prototype.get_cell_elements = function () {
452 return this.container.children("div.cell");
448 return this.container.children("div.cell");
453 };
449 };
454
450
455 /**
451 /**
456 * Get a particular cell element.
452 * Get a particular cell element.
457 *
453 *
458 * @method get_cell_element
454 * @method get_cell_element
459 * @param {Number} index An index of a cell to select
455 * @param {Number} index An index of a cell to select
460 * @return {jQuery} A selector of the given cell.
456 * @return {jQuery} A selector of the given cell.
461 */
457 */
462 Notebook.prototype.get_cell_element = function (index) {
458 Notebook.prototype.get_cell_element = function (index) {
463 var result = null;
459 var result = null;
464 var e = this.get_cell_elements().eq(index);
460 var e = this.get_cell_elements().eq(index);
465 if (e.length !== 0) {
461 if (e.length !== 0) {
466 result = e;
462 result = e;
467 }
463 }
468 return result;
464 return result;
469 };
465 };
470
466
471 /**
467 /**
472 * Count the cells in this notebook.
468 * Count the cells in this notebook.
473 *
469 *
474 * @method ncells
470 * @method ncells
475 * @return {Number} The number of cells in this notebook
471 * @return {Number} The number of cells in this notebook
476 */
472 */
477 Notebook.prototype.ncells = function () {
473 Notebook.prototype.ncells = function () {
478 return this.get_cell_elements().length;
474 return this.get_cell_elements().length;
479 };
475 };
480
476
481 /**
477 /**
482 * Get all Cell objects in this notebook.
478 * Get all Cell objects in this notebook.
483 *
479 *
484 * @method get_cells
480 * @method get_cells
485 * @return {Array} This notebook's Cell objects
481 * @return {Array} This notebook's Cell objects
486 */
482 */
487 // TODO: we are often calling cells as cells()[i], which we should optimize
483 // TODO: we are often calling cells as cells()[i], which we should optimize
488 // to cells(i) or a new method.
484 // to cells(i) or a new method.
489 Notebook.prototype.get_cells = function () {
485 Notebook.prototype.get_cells = function () {
490 return this.get_cell_elements().toArray().map(function (e) {
486 return this.get_cell_elements().toArray().map(function (e) {
491 return $(e).data("cell");
487 return $(e).data("cell");
492 });
488 });
493 };
489 };
494
490
495 /**
491 /**
496 * Get a Cell object from this notebook.
492 * Get a Cell object from this notebook.
497 *
493 *
498 * @method get_cell
494 * @method get_cell
499 * @param {Number} index An index of a cell to retrieve
495 * @param {Number} index An index of a cell to retrieve
500 * @return {Cell} A particular cell
496 * @return {Cell} A particular cell
501 */
497 */
502 Notebook.prototype.get_cell = function (index) {
498 Notebook.prototype.get_cell = function (index) {
503 var result = null;
499 var result = null;
504 var ce = this.get_cell_element(index);
500 var ce = this.get_cell_element(index);
505 if (ce !== null) {
501 if (ce !== null) {
506 result = ce.data('cell');
502 result = ce.data('cell');
507 }
503 }
508 return result;
504 return result;
509 }
505 }
510
506
511 /**
507 /**
512 * Get the cell below a given cell.
508 * Get the cell below a given cell.
513 *
509 *
514 * @method get_next_cell
510 * @method get_next_cell
515 * @param {Cell} cell The provided cell
511 * @param {Cell} cell The provided cell
516 * @return {Cell} The next cell
512 * @return {Cell} The next cell
517 */
513 */
518 Notebook.prototype.get_next_cell = function (cell) {
514 Notebook.prototype.get_next_cell = function (cell) {
519 var result = null;
515 var result = null;
520 var index = this.find_cell_index(cell);
516 var index = this.find_cell_index(cell);
521 if (this.is_valid_cell_index(index+1)) {
517 if (this.is_valid_cell_index(index+1)) {
522 result = this.get_cell(index+1);
518 result = this.get_cell(index+1);
523 }
519 }
524 return result;
520 return result;
525 }
521 }
526
522
527 /**
523 /**
528 * Get the cell above a given cell.
524 * Get the cell above a given cell.
529 *
525 *
530 * @method get_prev_cell
526 * @method get_prev_cell
531 * @param {Cell} cell The provided cell
527 * @param {Cell} cell The provided cell
532 * @return {Cell} The previous cell
528 * @return {Cell} The previous cell
533 */
529 */
534 Notebook.prototype.get_prev_cell = function (cell) {
530 Notebook.prototype.get_prev_cell = function (cell) {
535 // TODO: off-by-one
531 // TODO: off-by-one
536 // nb.get_prev_cell(nb.get_cell(1)) is null
532 // nb.get_prev_cell(nb.get_cell(1)) is null
537 var result = null;
533 var result = null;
538 var index = this.find_cell_index(cell);
534 var index = this.find_cell_index(cell);
539 if (index !== null && index > 1) {
535 if (index !== null && index > 1) {
540 result = this.get_cell(index-1);
536 result = this.get_cell(index-1);
541 }
537 }
542 return result;
538 return result;
543 }
539 }
544
540
545 /**
541 /**
546 * Get the numeric index of a given cell.
542 * Get the numeric index of a given cell.
547 *
543 *
548 * @method find_cell_index
544 * @method find_cell_index
549 * @param {Cell} cell The provided cell
545 * @param {Cell} cell The provided cell
550 * @return {Number} The cell's numeric index
546 * @return {Number} The cell's numeric index
551 */
547 */
552 Notebook.prototype.find_cell_index = function (cell) {
548 Notebook.prototype.find_cell_index = function (cell) {
553 var result = null;
549 var result = null;
554 this.get_cell_elements().filter(function (index) {
550 this.get_cell_elements().filter(function (index) {
555 if ($(this).data("cell") === cell) {
551 if ($(this).data("cell") === cell) {
556 result = index;
552 result = index;
557 };
553 };
558 });
554 });
559 return result;
555 return result;
560 };
556 };
561
557
562 /**
558 /**
563 * Get a given index , or the selected index if none is provided.
559 * Get a given index , or the selected index if none is provided.
564 *
560 *
565 * @method index_or_selected
561 * @method index_or_selected
566 * @param {Number} index A cell's index
562 * @param {Number} index A cell's index
567 * @return {Number} The given index, or selected index if none is provided.
563 * @return {Number} The given index, or selected index if none is provided.
568 */
564 */
569 Notebook.prototype.index_or_selected = function (index) {
565 Notebook.prototype.index_or_selected = function (index) {
570 var i;
566 var i;
571 if (index === undefined || index === null) {
567 if (index === undefined || index === null) {
572 i = this.get_selected_index();
568 i = this.get_selected_index();
573 if (i === null) {
569 if (i === null) {
574 i = 0;
570 i = 0;
575 }
571 }
576 } else {
572 } else {
577 i = index;
573 i = index;
578 }
574 }
579 return i;
575 return i;
580 };
576 };
581
577
582 /**
578 /**
583 * Get the currently selected cell.
579 * Get the currently selected cell.
584 * @method get_selected_cell
580 * @method get_selected_cell
585 * @return {Cell} The selected cell
581 * @return {Cell} The selected cell
586 */
582 */
587 Notebook.prototype.get_selected_cell = function () {
583 Notebook.prototype.get_selected_cell = function () {
588 var index = this.get_selected_index();
584 var index = this.get_selected_index();
589 return this.get_cell(index);
585 return this.get_cell(index);
590 };
586 };
591
587
592 /**
588 /**
593 * Check whether a cell index is valid.
589 * Check whether a cell index is valid.
594 *
590 *
595 * @method is_valid_cell_index
591 * @method is_valid_cell_index
596 * @param {Number} index A cell index
592 * @param {Number} index A cell index
597 * @return True if the index is valid, false otherwise
593 * @return True if the index is valid, false otherwise
598 */
594 */
599 Notebook.prototype.is_valid_cell_index = function (index) {
595 Notebook.prototype.is_valid_cell_index = function (index) {
600 if (index !== null && index >= 0 && index < this.ncells()) {
596 if (index !== null && index >= 0 && index < this.ncells()) {
601 return true;
597 return true;
602 } else {
598 } else {
603 return false;
599 return false;
604 };
600 };
605 }
601 }
606
602
607 /**
603 /**
608 * Get the index of the currently selected cell.
604 * Get the index of the currently selected cell.
609
605
610 * @method get_selected_index
606 * @method get_selected_index
611 * @return {Number} The selected cell's numeric index
607 * @return {Number} The selected cell's numeric index
612 */
608 */
613 Notebook.prototype.get_selected_index = function () {
609 Notebook.prototype.get_selected_index = function () {
614 var result = null;
610 var result = null;
615 this.get_cell_elements().filter(function (index) {
611 this.get_cell_elements().filter(function (index) {
616 if ($(this).data("cell").selected === true) {
612 if ($(this).data("cell").selected === true) {
617 result = index;
613 result = index;
618 };
614 };
619 });
615 });
620 return result;
616 return result;
621 };
617 };
622
618
623
619
624 // Cell selection.
620 // Cell selection.
625
621
626 /**
622 /**
627 * Programmatically select a cell.
623 * Programmatically select a cell.
628 *
624 *
629 * @method select
625 * @method select
630 * @param {Number} index A cell's index
626 * @param {Number} index A cell's index
631 * @return {Notebook} This notebook
627 * @return {Notebook} This notebook
632 */
628 */
633 Notebook.prototype.select = function (index) {
629 Notebook.prototype.select = function (index) {
634 if (this.is_valid_cell_index(index)) {
630 if (this.is_valid_cell_index(index)) {
635 var sindex = this.get_selected_index()
631 var sindex = this.get_selected_index()
636 if (sindex !== null && index !== sindex) {
632 if (sindex !== null && index !== sindex) {
637 this.get_cell(sindex).unselect();
633 this.get_cell(sindex).unselect();
638 };
634 };
639 var cell = this.get_cell(index);
635 var cell = this.get_cell(index);
640 cell.select();
636 cell.select();
641 if (cell.cell_type === 'heading') {
637 if (cell.cell_type === 'heading') {
642 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
638 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
643 {'cell_type':cell.cell_type,level:cell.level}
639 {'cell_type':cell.cell_type,level:cell.level}
644 );
640 );
645 } else {
641 } else {
646 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
642 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
647 {'cell_type':cell.cell_type}
643 {'cell_type':cell.cell_type}
648 );
644 );
649 };
645 };
650 };
646 };
651 return this;
647 return this;
652 };
648 };
653
649
654 /**
650 /**
655 * Programmatically select the next cell.
651 * Programmatically select the next cell.
656 *
652 *
657 * @method select_next
653 * @method select_next
658 * @return {Notebook} This notebook
654 * @return {Notebook} This notebook
659 */
655 */
660 Notebook.prototype.select_next = function () {
656 Notebook.prototype.select_next = function () {
661 var index = this.get_selected_index();
657 var index = this.get_selected_index();
662 this.select(index+1);
658 this.select(index+1);
663 return this;
659 return this;
664 };
660 };
665
661
666 /**
662 /**
667 * Programmatically select the previous cell.
663 * Programmatically select the previous cell.
668 *
664 *
669 * @method select_prev
665 * @method select_prev
670 * @return {Notebook} This notebook
666 * @return {Notebook} This notebook
671 */
667 */
672 Notebook.prototype.select_prev = function () {
668 Notebook.prototype.select_prev = function () {
673 var index = this.get_selected_index();
669 var index = this.get_selected_index();
674 this.select(index-1);
670 this.select(index-1);
675 return this;
671 return this;
676 };
672 };
677
673
678
674
679 // Cell movement
675 // Cell movement
680
676
681 /**
677 /**
682 * Move given (or selected) cell up and select it.
678 * Move given (or selected) cell up and select it.
683 *
679 *
684 * @method move_cell_up
680 * @method move_cell_up
685 * @param [index] {integer} cell index
681 * @param [index] {integer} cell index
686 * @return {Notebook} This notebook
682 * @return {Notebook} This notebook
687 **/
683 **/
688 Notebook.prototype.move_cell_up = function (index) {
684 Notebook.prototype.move_cell_up = function (index) {
689 var i = this.index_or_selected(index);
685 var i = this.index_or_selected(index);
690 if (this.is_valid_cell_index(i) && i > 0) {
686 if (this.is_valid_cell_index(i) && i > 0) {
691 var pivot = this.get_cell_element(i-1);
687 var pivot = this.get_cell_element(i-1);
692 var tomove = this.get_cell_element(i);
688 var tomove = this.get_cell_element(i);
693 if (pivot !== null && tomove !== null) {
689 if (pivot !== null && tomove !== null) {
694 tomove.detach();
690 tomove.detach();
695 pivot.before(tomove);
691 pivot.before(tomove);
696 this.select(i-1);
692 this.select(i-1);
697 };
693 };
698 this.set_dirty(true);
694 this.set_dirty(true);
699 };
695 };
700 return this;
696 return this;
701 };
697 };
702
698
703
699
704 /**
700 /**
705 * Move given (or selected) cell down and select it
701 * Move given (or selected) cell down and select it
706 *
702 *
707 * @method move_cell_down
703 * @method move_cell_down
708 * @param [index] {integer} cell index
704 * @param [index] {integer} cell index
709 * @return {Notebook} This notebook
705 * @return {Notebook} This notebook
710 **/
706 **/
711 Notebook.prototype.move_cell_down = function (index) {
707 Notebook.prototype.move_cell_down = function (index) {
712 var i = this.index_or_selected(index);
708 var i = this.index_or_selected(index);
713 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
709 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
714 var pivot = this.get_cell_element(i+1);
710 var pivot = this.get_cell_element(i+1);
715 var tomove = this.get_cell_element(i);
711 var tomove = this.get_cell_element(i);
716 if (pivot !== null && tomove !== null) {
712 if (pivot !== null && tomove !== null) {
717 tomove.detach();
713 tomove.detach();
718 pivot.after(tomove);
714 pivot.after(tomove);
719 this.select(i+1);
715 this.select(i+1);
720 };
716 };
721 };
717 };
722 this.set_dirty();
718 this.set_dirty();
723 return this;
719 return this;
724 };
720 };
725
721
726
722
727 // Insertion, deletion.
723 // Insertion, deletion.
728
724
729 /**
725 /**
730 * Delete a cell from the notebook.
726 * Delete a cell from the notebook.
731 *
727 *
732 * @method delete_cell
728 * @method delete_cell
733 * @param [index] A cell's numeric index
729 * @param [index] A cell's numeric index
734 * @return {Notebook} This notebook
730 * @return {Notebook} This notebook
735 */
731 */
736 Notebook.prototype.delete_cell = function (index) {
732 Notebook.prototype.delete_cell = function (index) {
737 var i = this.index_or_selected(index);
733 var i = this.index_or_selected(index);
738 var cell = this.get_selected_cell();
734 var cell = this.get_selected_cell();
739 this.undelete_backup = cell.toJSON();
735 this.undelete_backup = cell.toJSON();
740 $('#undelete_cell').removeClass('disabled');
736 $('#undelete_cell').removeClass('disabled');
741 if (this.is_valid_cell_index(i)) {
737 if (this.is_valid_cell_index(i)) {
742 var ce = this.get_cell_element(i);
738 var ce = this.get_cell_element(i);
743 ce.remove();
739 ce.remove();
744 if (i === (this.ncells())) {
740 if (i === (this.ncells())) {
745 this.select(i-1);
741 this.select(i-1);
746 this.undelete_index = i - 1;
742 this.undelete_index = i - 1;
747 this.undelete_below = true;
743 this.undelete_below = true;
748 } else {
744 } else {
749 this.select(i);
745 this.select(i);
750 this.undelete_index = i;
746 this.undelete_index = i;
751 this.undelete_below = false;
747 this.undelete_below = false;
752 };
748 };
753 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
749 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
754 this.set_dirty(true);
750 this.set_dirty(true);
755 };
751 };
756 return this;
752 return this;
757 };
753 };
758
754
759 /**
755 /**
760 * Insert a cell so that after insertion the cell is at given index.
756 * Insert a cell so that after insertion the cell is at given index.
761 *
757 *
762 * Similar to insert_above, but index parameter is mandatory
758 * Similar to insert_above, but index parameter is mandatory
763 *
759 *
764 * Index will be brought back into the accissible range [0,n]
760 * Index will be brought back into the accissible range [0,n]
765 *
761 *
766 * @method insert_cell_at_index
762 * @method insert_cell_at_index
767 * @param type {string} in ['code','markdown','heading']
763 * @param type {string} in ['code','markdown','heading']
768 * @param [index] {int} a valid index where to inser cell
764 * @param [index] {int} a valid index where to inser cell
769 *
765 *
770 * @return cell {cell|null} created cell or null
766 * @return cell {cell|null} created cell or null
771 **/
767 **/
772 Notebook.prototype.insert_cell_at_index = function(type, index){
768 Notebook.prototype.insert_cell_at_index = function(type, index){
773
769
774 var ncells = this.ncells();
770 var ncells = this.ncells();
775 var index = Math.min(index,ncells);
771 var index = Math.min(index,ncells);
776 index = Math.max(index,0);
772 index = Math.max(index,0);
777 var cell = null;
773 var cell = null;
778
774
779 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
775 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
780 if (type === 'code') {
776 if (type === 'code') {
781 cell = new IPython.CodeCell(this.kernel);
777 cell = new IPython.CodeCell(this.kernel);
782 cell.set_input_prompt();
778 cell.set_input_prompt();
783 } else if (type === 'markdown') {
779 } else if (type === 'markdown') {
784 cell = new IPython.MarkdownCell();
780 cell = new IPython.MarkdownCell();
785 } else if (type === 'raw') {
781 } else if (type === 'raw') {
786 cell = new IPython.RawCell();
782 cell = new IPython.RawCell();
787 } else if (type === 'heading') {
783 } else if (type === 'heading') {
788 cell = new IPython.HeadingCell();
784 cell = new IPython.HeadingCell();
789 }
785 }
790
786
791 if(this._insert_element_at_index(cell.element,index)){
787 if(this._insert_element_at_index(cell.element,index)){
792 cell.render();
788 cell.render();
793 this.select(this.find_cell_index(cell));
789 this.select(this.find_cell_index(cell));
794 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
790 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
795 this.set_dirty(true);
791 this.set_dirty(true);
796 }
792 }
797 }
793 }
798 return cell;
794 return cell;
799
795
800 };
796 };
801
797
802 /**
798 /**
803 * Insert an element at given cell index.
799 * Insert an element at given cell index.
804 *
800 *
805 * @method _insert_element_at_index
801 * @method _insert_element_at_index
806 * @param element {dom element} a cell element
802 * @param element {dom element} a cell element
807 * @param [index] {int} a valid index where to inser cell
803 * @param [index] {int} a valid index where to inser cell
808 * @private
804 * @private
809 *
805 *
810 * return true if everything whent fine.
806 * return true if everything whent fine.
811 **/
807 **/
812 Notebook.prototype._insert_element_at_index = function(element, index){
808 Notebook.prototype._insert_element_at_index = function(element, index){
813 if (element === undefined){
809 if (element === undefined){
814 return false;
810 return false;
815 }
811 }
816
812
817 var ncells = this.ncells();
813 var ncells = this.ncells();
818
814
819 if (ncells === 0) {
815 if (ncells === 0) {
820 // special case append if empty
816 // special case append if empty
821 this.element.find('div.end_space').before(element);
817 this.element.find('div.end_space').before(element);
822 } else if ( ncells === index ) {
818 } else if ( ncells === index ) {
823 // special case append it the end, but not empty
819 // special case append it the end, but not empty
824 this.get_cell_element(index-1).after(element);
820 this.get_cell_element(index-1).after(element);
825 } else if (this.is_valid_cell_index(index)) {
821 } else if (this.is_valid_cell_index(index)) {
826 // otherwise always somewhere to append to
822 // otherwise always somewhere to append to
827 this.get_cell_element(index).before(element);
823 this.get_cell_element(index).before(element);
828 } else {
824 } else {
829 return false;
825 return false;
830 }
826 }
831
827
832 if (this.undelete_index !== null && index <= this.undelete_index) {
828 if (this.undelete_index !== null && index <= this.undelete_index) {
833 this.undelete_index = this.undelete_index + 1;
829 this.undelete_index = this.undelete_index + 1;
834 this.set_dirty(true);
830 this.set_dirty(true);
835 }
831 }
836 return true;
832 return true;
837 };
833 };
838
834
839 /**
835 /**
840 * Insert a cell of given type above given index, or at top
836 * Insert a cell of given type above given index, or at top
841 * of notebook if index smaller than 0.
837 * of notebook if index smaller than 0.
842 *
838 *
843 * default index value is the one of currently selected cell
839 * default index value is the one of currently selected cell
844 *
840 *
845 * @method insert_cell_above
841 * @method insert_cell_above
846 * @param type {string} cell type
842 * @param type {string} cell type
847 * @param [index] {integer}
843 * @param [index] {integer}
848 *
844 *
849 * @return handle to created cell or null
845 * @return handle to created cell or null
850 **/
846 **/
851 Notebook.prototype.insert_cell_above = function (type, index) {
847 Notebook.prototype.insert_cell_above = function (type, index) {
852 index = this.index_or_selected(index);
848 index = this.index_or_selected(index);
853 return this.insert_cell_at_index(type, index);
849 return this.insert_cell_at_index(type, index);
854 };
850 };
855
851
856 /**
852 /**
857 * Insert a cell of given type below given index, or at bottom
853 * Insert a cell of given type below given index, or at bottom
858 * of notebook if index greater thatn number of cell
854 * of notebook if index greater thatn number of cell
859 *
855 *
860 * default index value is the one of currently selected cell
856 * default index value is the one of currently selected cell
861 *
857 *
862 * @method insert_cell_below
858 * @method insert_cell_below
863 * @param type {string} cell type
859 * @param type {string} cell type
864 * @param [index] {integer}
860 * @param [index] {integer}
865 *
861 *
866 * @return handle to created cell or null
862 * @return handle to created cell or null
867 *
863 *
868 **/
864 **/
869 Notebook.prototype.insert_cell_below = function (type, index) {
865 Notebook.prototype.insert_cell_below = function (type, index) {
870 index = this.index_or_selected(index);
866 index = this.index_or_selected(index);
871 return this.insert_cell_at_index(type, index+1);
867 return this.insert_cell_at_index(type, index+1);
872 };
868 };
873
869
874
870
875 /**
871 /**
876 * Insert cell at end of notebook
872 * Insert cell at end of notebook
877 *
873 *
878 * @method insert_cell_at_bottom
874 * @method insert_cell_at_bottom
879 * @param {String} type cell type
875 * @param {String} type cell type
880 *
876 *
881 * @return the added cell; or null
877 * @return the added cell; or null
882 **/
878 **/
883 Notebook.prototype.insert_cell_at_bottom = function (type){
879 Notebook.prototype.insert_cell_at_bottom = function (type){
884 var len = this.ncells();
880 var len = this.ncells();
885 return this.insert_cell_below(type,len-1);
881 return this.insert_cell_below(type,len-1);
886 };
882 };
887
883
888 /**
884 /**
889 * Turn a cell into a code cell.
885 * Turn a cell into a code cell.
890 *
886 *
891 * @method to_code
887 * @method to_code
892 * @param {Number} [index] A cell's index
888 * @param {Number} [index] A cell's index
893 */
889 */
894 Notebook.prototype.to_code = function (index) {
890 Notebook.prototype.to_code = function (index) {
895 var i = this.index_or_selected(index);
891 var i = this.index_or_selected(index);
896 if (this.is_valid_cell_index(i)) {
892 if (this.is_valid_cell_index(i)) {
897 var source_element = this.get_cell_element(i);
893 var source_element = this.get_cell_element(i);
898 var source_cell = source_element.data("cell");
894 var source_cell = source_element.data("cell");
899 if (!(source_cell instanceof IPython.CodeCell)) {
895 if (!(source_cell instanceof IPython.CodeCell)) {
900 var target_cell = this.insert_cell_below('code',i);
896 var target_cell = this.insert_cell_below('code',i);
901 var text = source_cell.get_text();
897 var text = source_cell.get_text();
902 if (text === source_cell.placeholder) {
898 if (text === source_cell.placeholder) {
903 text = '';
899 text = '';
904 }
900 }
905 target_cell.set_text(text);
901 target_cell.set_text(text);
906 // make this value the starting point, so that we can only undo
902 // make this value the starting point, so that we can only undo
907 // to this state, instead of a blank cell
903 // to this state, instead of a blank cell
908 target_cell.code_mirror.clearHistory();
904 target_cell.code_mirror.clearHistory();
909 source_element.remove();
905 source_element.remove();
910 this.set_dirty(true);
906 this.set_dirty(true);
911 };
907 };
912 };
908 };
913 };
909 };
914
910
915 /**
911 /**
916 * Turn a cell into a Markdown cell.
912 * Turn a cell into a Markdown cell.
917 *
913 *
918 * @method to_markdown
914 * @method to_markdown
919 * @param {Number} [index] A cell's index
915 * @param {Number} [index] A cell's index
920 */
916 */
921 Notebook.prototype.to_markdown = function (index) {
917 Notebook.prototype.to_markdown = function (index) {
922 var i = this.index_or_selected(index);
918 var i = this.index_or_selected(index);
923 if (this.is_valid_cell_index(i)) {
919 if (this.is_valid_cell_index(i)) {
924 var source_element = this.get_cell_element(i);
920 var source_element = this.get_cell_element(i);
925 var source_cell = source_element.data("cell");
921 var source_cell = source_element.data("cell");
926 if (!(source_cell instanceof IPython.MarkdownCell)) {
922 if (!(source_cell instanceof IPython.MarkdownCell)) {
927 var target_cell = this.insert_cell_below('markdown',i);
923 var target_cell = this.insert_cell_below('markdown',i);
928 var text = source_cell.get_text();
924 var text = source_cell.get_text();
929 if (text === source_cell.placeholder) {
925 if (text === source_cell.placeholder) {
930 text = '';
926 text = '';
931 };
927 };
932 // The edit must come before the set_text.
928 // The edit must come before the set_text.
933 target_cell.edit();
929 target_cell.edit();
934 target_cell.set_text(text);
930 target_cell.set_text(text);
935 // make this value the starting point, so that we can only undo
931 // make this value the starting point, so that we can only undo
936 // to this state, instead of a blank cell
932 // to this state, instead of a blank cell
937 target_cell.code_mirror.clearHistory();
933 target_cell.code_mirror.clearHistory();
938 source_element.remove();
934 source_element.remove();
939 this.set_dirty(true);
935 this.set_dirty(true);
940 };
936 };
941 };
937 };
942 };
938 };
943
939
944 /**
940 /**
945 * Turn a cell into a raw text cell.
941 * Turn a cell into a raw text cell.
946 *
942 *
947 * @method to_raw
943 * @method to_raw
948 * @param {Number} [index] A cell's index
944 * @param {Number} [index] A cell's index
949 */
945 */
950 Notebook.prototype.to_raw = function (index) {
946 Notebook.prototype.to_raw = function (index) {
951 var i = this.index_or_selected(index);
947 var i = this.index_or_selected(index);
952 if (this.is_valid_cell_index(i)) {
948 if (this.is_valid_cell_index(i)) {
953 var source_element = this.get_cell_element(i);
949 var source_element = this.get_cell_element(i);
954 var source_cell = source_element.data("cell");
950 var source_cell = source_element.data("cell");
955 var target_cell = null;
951 var target_cell = null;
956 if (!(source_cell instanceof IPython.RawCell)) {
952 if (!(source_cell instanceof IPython.RawCell)) {
957 target_cell = this.insert_cell_below('raw',i);
953 target_cell = this.insert_cell_below('raw',i);
958 var text = source_cell.get_text();
954 var text = source_cell.get_text();
959 if (text === source_cell.placeholder) {
955 if (text === source_cell.placeholder) {
960 text = '';
956 text = '';
961 };
957 };
962 // The edit must come before the set_text.
958 // The edit must come before the set_text.
963 target_cell.edit();
959 target_cell.edit();
964 target_cell.set_text(text);
960 target_cell.set_text(text);
965 // make this value the starting point, so that we can only undo
961 // make this value the starting point, so that we can only undo
966 // to this state, instead of a blank cell
962 // to this state, instead of a blank cell
967 target_cell.code_mirror.clearHistory();
963 target_cell.code_mirror.clearHistory();
968 source_element.remove();
964 source_element.remove();
969 this.set_dirty(true);
965 this.set_dirty(true);
970 };
966 };
971 };
967 };
972 };
968 };
973
969
974 /**
970 /**
975 * Turn a cell into a heading cell.
971 * Turn a cell into a heading cell.
976 *
972 *
977 * @method to_heading
973 * @method to_heading
978 * @param {Number} [index] A cell's index
974 * @param {Number} [index] A cell's index
979 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
975 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
980 */
976 */
981 Notebook.prototype.to_heading = function (index, level) {
977 Notebook.prototype.to_heading = function (index, level) {
982 level = level || 1;
978 level = level || 1;
983 var i = this.index_or_selected(index);
979 var i = this.index_or_selected(index);
984 if (this.is_valid_cell_index(i)) {
980 if (this.is_valid_cell_index(i)) {
985 var source_element = this.get_cell_element(i);
981 var source_element = this.get_cell_element(i);
986 var source_cell = source_element.data("cell");
982 var source_cell = source_element.data("cell");
987 var target_cell = null;
983 var target_cell = null;
988 if (source_cell instanceof IPython.HeadingCell) {
984 if (source_cell instanceof IPython.HeadingCell) {
989 source_cell.set_level(level);
985 source_cell.set_level(level);
990 } else {
986 } else {
991 target_cell = this.insert_cell_below('heading',i);
987 target_cell = this.insert_cell_below('heading',i);
992 var text = source_cell.get_text();
988 var text = source_cell.get_text();
993 if (text === source_cell.placeholder) {
989 if (text === source_cell.placeholder) {
994 text = '';
990 text = '';
995 };
991 };
996 // The edit must come before the set_text.
992 // The edit must come before the set_text.
997 target_cell.set_level(level);
993 target_cell.set_level(level);
998 target_cell.edit();
994 target_cell.edit();
999 target_cell.set_text(text);
995 target_cell.set_text(text);
1000 // make this value the starting point, so that we can only undo
996 // make this value the starting point, so that we can only undo
1001 // to this state, instead of a blank cell
997 // to this state, instead of a blank cell
1002 target_cell.code_mirror.clearHistory();
998 target_cell.code_mirror.clearHistory();
1003 source_element.remove();
999 source_element.remove();
1004 this.set_dirty(true);
1000 this.set_dirty(true);
1005 };
1001 };
1006 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1002 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1007 {'cell_type':'heading',level:level}
1003 {'cell_type':'heading',level:level}
1008 );
1004 );
1009 };
1005 };
1010 };
1006 };
1011
1007
1012
1008
1013 // Cut/Copy/Paste
1009 // Cut/Copy/Paste
1014
1010
1015 /**
1011 /**
1016 * Enable UI elements for pasting cells.
1012 * Enable UI elements for pasting cells.
1017 *
1013 *
1018 * @method enable_paste
1014 * @method enable_paste
1019 */
1015 */
1020 Notebook.prototype.enable_paste = function () {
1016 Notebook.prototype.enable_paste = function () {
1021 var that = this;
1017 var that = this;
1022 if (!this.paste_enabled) {
1018 if (!this.paste_enabled) {
1023 $('#paste_cell_replace').removeClass('disabled')
1019 $('#paste_cell_replace').removeClass('disabled')
1024 .on('click', function () {that.paste_cell_replace();});
1020 .on('click', function () {that.paste_cell_replace();});
1025 $('#paste_cell_above').removeClass('disabled')
1021 $('#paste_cell_above').removeClass('disabled')
1026 .on('click', function () {that.paste_cell_above();});
1022 .on('click', function () {that.paste_cell_above();});
1027 $('#paste_cell_below').removeClass('disabled')
1023 $('#paste_cell_below').removeClass('disabled')
1028 .on('click', function () {that.paste_cell_below();});
1024 .on('click', function () {that.paste_cell_below();});
1029 this.paste_enabled = true;
1025 this.paste_enabled = true;
1030 };
1026 };
1031 };
1027 };
1032
1028
1033 /**
1029 /**
1034 * Disable UI elements for pasting cells.
1030 * Disable UI elements for pasting cells.
1035 *
1031 *
1036 * @method disable_paste
1032 * @method disable_paste
1037 */
1033 */
1038 Notebook.prototype.disable_paste = function () {
1034 Notebook.prototype.disable_paste = function () {
1039 if (this.paste_enabled) {
1035 if (this.paste_enabled) {
1040 $('#paste_cell_replace').addClass('disabled').off('click');
1036 $('#paste_cell_replace').addClass('disabled').off('click');
1041 $('#paste_cell_above').addClass('disabled').off('click');
1037 $('#paste_cell_above').addClass('disabled').off('click');
1042 $('#paste_cell_below').addClass('disabled').off('click');
1038 $('#paste_cell_below').addClass('disabled').off('click');
1043 this.paste_enabled = false;
1039 this.paste_enabled = false;
1044 };
1040 };
1045 };
1041 };
1046
1042
1047 /**
1043 /**
1048 * Cut a cell.
1044 * Cut a cell.
1049 *
1045 *
1050 * @method cut_cell
1046 * @method cut_cell
1051 */
1047 */
1052 Notebook.prototype.cut_cell = function () {
1048 Notebook.prototype.cut_cell = function () {
1053 this.copy_cell();
1049 this.copy_cell();
1054 this.delete_cell();
1050 this.delete_cell();
1055 }
1051 }
1056
1052
1057 /**
1053 /**
1058 * Copy a cell.
1054 * Copy a cell.
1059 *
1055 *
1060 * @method copy_cell
1056 * @method copy_cell
1061 */
1057 */
1062 Notebook.prototype.copy_cell = function () {
1058 Notebook.prototype.copy_cell = function () {
1063 var cell = this.get_selected_cell();
1059 var cell = this.get_selected_cell();
1064 this.clipboard = cell.toJSON();
1060 this.clipboard = cell.toJSON();
1065 this.enable_paste();
1061 this.enable_paste();
1066 };
1062 };
1067
1063
1068 /**
1064 /**
1069 * Replace the selected cell with a cell in the clipboard.
1065 * Replace the selected cell with a cell in the clipboard.
1070 *
1066 *
1071 * @method paste_cell_replace
1067 * @method paste_cell_replace
1072 */
1068 */
1073 Notebook.prototype.paste_cell_replace = function () {
1069 Notebook.prototype.paste_cell_replace = function () {
1074 if (this.clipboard !== null && this.paste_enabled) {
1070 if (this.clipboard !== null && this.paste_enabled) {
1075 var cell_data = this.clipboard;
1071 var cell_data = this.clipboard;
1076 var new_cell = this.insert_cell_above(cell_data.cell_type);
1072 var new_cell = this.insert_cell_above(cell_data.cell_type);
1077 new_cell.fromJSON(cell_data);
1073 new_cell.fromJSON(cell_data);
1078 var old_cell = this.get_next_cell(new_cell);
1074 var old_cell = this.get_next_cell(new_cell);
1079 this.delete_cell(this.find_cell_index(old_cell));
1075 this.delete_cell(this.find_cell_index(old_cell));
1080 this.select(this.find_cell_index(new_cell));
1076 this.select(this.find_cell_index(new_cell));
1081 };
1077 };
1082 };
1078 };
1083
1079
1084 /**
1080 /**
1085 * Paste a cell from the clipboard above the selected cell.
1081 * Paste a cell from the clipboard above the selected cell.
1086 *
1082 *
1087 * @method paste_cell_above
1083 * @method paste_cell_above
1088 */
1084 */
1089 Notebook.prototype.paste_cell_above = function () {
1085 Notebook.prototype.paste_cell_above = function () {
1090 if (this.clipboard !== null && this.paste_enabled) {
1086 if (this.clipboard !== null && this.paste_enabled) {
1091 var cell_data = this.clipboard;
1087 var cell_data = this.clipboard;
1092 var new_cell = this.insert_cell_above(cell_data.cell_type);
1088 var new_cell = this.insert_cell_above(cell_data.cell_type);
1093 new_cell.fromJSON(cell_data);
1089 new_cell.fromJSON(cell_data);
1094 };
1090 };
1095 };
1091 };
1096
1092
1097 /**
1093 /**
1098 * Paste a cell from the clipboard below the selected cell.
1094 * Paste a cell from the clipboard below the selected cell.
1099 *
1095 *
1100 * @method paste_cell_below
1096 * @method paste_cell_below
1101 */
1097 */
1102 Notebook.prototype.paste_cell_below = function () {
1098 Notebook.prototype.paste_cell_below = function () {
1103 if (this.clipboard !== null && this.paste_enabled) {
1099 if (this.clipboard !== null && this.paste_enabled) {
1104 var cell_data = this.clipboard;
1100 var cell_data = this.clipboard;
1105 var new_cell = this.insert_cell_below(cell_data.cell_type);
1101 var new_cell = this.insert_cell_below(cell_data.cell_type);
1106 new_cell.fromJSON(cell_data);
1102 new_cell.fromJSON(cell_data);
1107 };
1103 };
1108 };
1104 };
1109
1105
1110 // Cell undelete
1106 // Cell undelete
1111
1107
1112 /**
1108 /**
1113 * Restore the most recently deleted cell.
1109 * Restore the most recently deleted cell.
1114 *
1110 *
1115 * @method undelete
1111 * @method undelete
1116 */
1112 */
1117 Notebook.prototype.undelete = function() {
1113 Notebook.prototype.undelete = function() {
1118 if (this.undelete_backup !== null && this.undelete_index !== null) {
1114 if (this.undelete_backup !== null && this.undelete_index !== null) {
1119 var current_index = this.get_selected_index();
1115 var current_index = this.get_selected_index();
1120 if (this.undelete_index < current_index) {
1116 if (this.undelete_index < current_index) {
1121 current_index = current_index + 1;
1117 current_index = current_index + 1;
1122 }
1118 }
1123 if (this.undelete_index >= this.ncells()) {
1119 if (this.undelete_index >= this.ncells()) {
1124 this.select(this.ncells() - 1);
1120 this.select(this.ncells() - 1);
1125 }
1121 }
1126 else {
1122 else {
1127 this.select(this.undelete_index);
1123 this.select(this.undelete_index);
1128 }
1124 }
1129 var cell_data = this.undelete_backup;
1125 var cell_data = this.undelete_backup;
1130 var new_cell = null;
1126 var new_cell = null;
1131 if (this.undelete_below) {
1127 if (this.undelete_below) {
1132 new_cell = this.insert_cell_below(cell_data.cell_type);
1128 new_cell = this.insert_cell_below(cell_data.cell_type);
1133 } else {
1129 } else {
1134 new_cell = this.insert_cell_above(cell_data.cell_type);
1130 new_cell = this.insert_cell_above(cell_data.cell_type);
1135 }
1131 }
1136 new_cell.fromJSON(cell_data);
1132 new_cell.fromJSON(cell_data);
1137 this.select(current_index);
1133 this.select(current_index);
1138 this.undelete_backup = null;
1134 this.undelete_backup = null;
1139 this.undelete_index = null;
1135 this.undelete_index = null;
1140 }
1136 }
1141 $('#undelete_cell').addClass('disabled');
1137 $('#undelete_cell').addClass('disabled');
1142 }
1138 }
1143
1139
1144 // Split/merge
1140 // Split/merge
1145
1141
1146 /**
1142 /**
1147 * Split the selected cell into two, at the cursor.
1143 * Split the selected cell into two, at the cursor.
1148 *
1144 *
1149 * @method split_cell
1145 * @method split_cell
1150 */
1146 */
1151 Notebook.prototype.split_cell = function () {
1147 Notebook.prototype.split_cell = function () {
1152 // Todo: implement spliting for other cell types.
1148 // Todo: implement spliting for other cell types.
1153 var cell = this.get_selected_cell();
1149 var cell = this.get_selected_cell();
1154 if (cell.is_splittable()) {
1150 if (cell.is_splittable()) {
1155 var texta = cell.get_pre_cursor();
1151 var texta = cell.get_pre_cursor();
1156 var textb = cell.get_post_cursor();
1152 var textb = cell.get_post_cursor();
1157 if (cell instanceof IPython.CodeCell) {
1153 if (cell instanceof IPython.CodeCell) {
1158 cell.set_text(texta);
1154 cell.set_text(texta);
1159 var new_cell = this.insert_cell_below('code');
1155 var new_cell = this.insert_cell_below('code');
1160 new_cell.set_text(textb);
1156 new_cell.set_text(textb);
1161 } else if (cell instanceof IPython.MarkdownCell) {
1157 } else if (cell instanceof IPython.MarkdownCell) {
1162 cell.set_text(texta);
1158 cell.set_text(texta);
1163 cell.render();
1159 cell.render();
1164 var new_cell = this.insert_cell_below('markdown');
1160 var new_cell = this.insert_cell_below('markdown');
1165 new_cell.edit(); // editor must be visible to call set_text
1161 new_cell.edit(); // editor must be visible to call set_text
1166 new_cell.set_text(textb);
1162 new_cell.set_text(textb);
1167 new_cell.render();
1163 new_cell.render();
1168 }
1164 }
1169 };
1165 };
1170 };
1166 };
1171
1167
1172 /**
1168 /**
1173 * Combine the selected cell into the cell above it.
1169 * Combine the selected cell into the cell above it.
1174 *
1170 *
1175 * @method merge_cell_above
1171 * @method merge_cell_above
1176 */
1172 */
1177 Notebook.prototype.merge_cell_above = function () {
1173 Notebook.prototype.merge_cell_above = function () {
1178 var index = this.get_selected_index();
1174 var index = this.get_selected_index();
1179 var cell = this.get_cell(index);
1175 var cell = this.get_cell(index);
1180 if (index > 0) {
1176 if (index > 0) {
1181 var upper_cell = this.get_cell(index-1);
1177 var upper_cell = this.get_cell(index-1);
1182 var upper_text = upper_cell.get_text();
1178 var upper_text = upper_cell.get_text();
1183 var text = cell.get_text();
1179 var text = cell.get_text();
1184 if (cell instanceof IPython.CodeCell) {
1180 if (cell instanceof IPython.CodeCell) {
1185 cell.set_text(upper_text+'\n'+text);
1181 cell.set_text(upper_text+'\n'+text);
1186 } else if (cell instanceof IPython.MarkdownCell) {
1182 } else if (cell instanceof IPython.MarkdownCell) {
1187 cell.edit();
1183 cell.edit();
1188 cell.set_text(upper_text+'\n'+text);
1184 cell.set_text(upper_text+'\n'+text);
1189 cell.render();
1185 cell.render();
1190 };
1186 };
1191 this.delete_cell(index-1);
1187 this.delete_cell(index-1);
1192 this.select(this.find_cell_index(cell));
1188 this.select(this.find_cell_index(cell));
1193 };
1189 };
1194 };
1190 };
1195
1191
1196 /**
1192 /**
1197 * Combine the selected cell into the cell below it.
1193 * Combine the selected cell into the cell below it.
1198 *
1194 *
1199 * @method merge_cell_below
1195 * @method merge_cell_below
1200 */
1196 */
1201 Notebook.prototype.merge_cell_below = function () {
1197 Notebook.prototype.merge_cell_below = function () {
1202 var index = this.get_selected_index();
1198 var index = this.get_selected_index();
1203 var cell = this.get_cell(index);
1199 var cell = this.get_cell(index);
1204 if (index < this.ncells()-1) {
1200 if (index < this.ncells()-1) {
1205 var lower_cell = this.get_cell(index+1);
1201 var lower_cell = this.get_cell(index+1);
1206 var lower_text = lower_cell.get_text();
1202 var lower_text = lower_cell.get_text();
1207 var text = cell.get_text();
1203 var text = cell.get_text();
1208 if (cell instanceof IPython.CodeCell) {
1204 if (cell instanceof IPython.CodeCell) {
1209 cell.set_text(text+'\n'+lower_text);
1205 cell.set_text(text+'\n'+lower_text);
1210 } else if (cell instanceof IPython.MarkdownCell) {
1206 } else if (cell instanceof IPython.MarkdownCell) {
1211 cell.edit();
1207 cell.edit();
1212 cell.set_text(text+'\n'+lower_text);
1208 cell.set_text(text+'\n'+lower_text);
1213 cell.render();
1209 cell.render();
1214 };
1210 };
1215 this.delete_cell(index+1);
1211 this.delete_cell(index+1);
1216 this.select(this.find_cell_index(cell));
1212 this.select(this.find_cell_index(cell));
1217 };
1213 };
1218 };
1214 };
1219
1215
1220
1216
1221 // Cell collapsing and output clearing
1217 // Cell collapsing and output clearing
1222
1218
1223 /**
1219 /**
1224 * Hide a cell's output.
1220 * Hide a cell's output.
1225 *
1221 *
1226 * @method collapse
1222 * @method collapse
1227 * @param {Number} index A cell's numeric index
1223 * @param {Number} index A cell's numeric index
1228 */
1224 */
1229 Notebook.prototype.collapse = function (index) {
1225 Notebook.prototype.collapse = function (index) {
1230 var i = this.index_or_selected(index);
1226 var i = this.index_or_selected(index);
1231 this.get_cell(i).collapse();
1227 this.get_cell(i).collapse();
1232 this.set_dirty(true);
1228 this.set_dirty(true);
1233 };
1229 };
1234
1230
1235 /**
1231 /**
1236 * Show a cell's output.
1232 * Show a cell's output.
1237 *
1233 *
1238 * @method expand
1234 * @method expand
1239 * @param {Number} index A cell's numeric index
1235 * @param {Number} index A cell's numeric index
1240 */
1236 */
1241 Notebook.prototype.expand = function (index) {
1237 Notebook.prototype.expand = function (index) {
1242 var i = this.index_or_selected(index);
1238 var i = this.index_or_selected(index);
1243 this.get_cell(i).expand();
1239 this.get_cell(i).expand();
1244 this.set_dirty(true);
1240 this.set_dirty(true);
1245 };
1241 };
1246
1242
1247 /** Toggle whether a cell's output is collapsed or expanded.
1243 /** Toggle whether a cell's output is collapsed or expanded.
1248 *
1244 *
1249 * @method toggle_output
1245 * @method toggle_output
1250 * @param {Number} index A cell's numeric index
1246 * @param {Number} index A cell's numeric index
1251 */
1247 */
1252 Notebook.prototype.toggle_output = function (index) {
1248 Notebook.prototype.toggle_output = function (index) {
1253 var i = this.index_or_selected(index);
1249 var i = this.index_or_selected(index);
1254 this.get_cell(i).toggle_output();
1250 this.get_cell(i).toggle_output();
1255 this.set_dirty(true);
1251 this.set_dirty(true);
1256 };
1252 };
1257
1253
1258 /**
1254 /**
1259 * Toggle a scrollbar for long cell outputs.
1255 * Toggle a scrollbar for long cell outputs.
1260 *
1256 *
1261 * @method toggle_output_scroll
1257 * @method toggle_output_scroll
1262 * @param {Number} index A cell's numeric index
1258 * @param {Number} index A cell's numeric index
1263 */
1259 */
1264 Notebook.prototype.toggle_output_scroll = function (index) {
1260 Notebook.prototype.toggle_output_scroll = function (index) {
1265 var i = this.index_or_selected(index);
1261 var i = this.index_or_selected(index);
1266 this.get_cell(i).toggle_output_scroll();
1262 this.get_cell(i).toggle_output_scroll();
1267 };
1263 };
1268
1264
1269 /**
1265 /**
1270 * Hide each code cell's output area.
1266 * Hide each code cell's output area.
1271 *
1267 *
1272 * @method collapse_all_output
1268 * @method collapse_all_output
1273 */
1269 */
1274 Notebook.prototype.collapse_all_output = function () {
1270 Notebook.prototype.collapse_all_output = function () {
1275 var ncells = this.ncells();
1271 var ncells = this.ncells();
1276 var cells = this.get_cells();
1272 var cells = this.get_cells();
1277 for (var i=0; i<ncells; i++) {
1273 for (var i=0; i<ncells; i++) {
1278 if (cells[i] instanceof IPython.CodeCell) {
1274 if (cells[i] instanceof IPython.CodeCell) {
1279 cells[i].output_area.collapse();
1275 cells[i].output_area.collapse();
1280 }
1276 }
1281 };
1277 };
1282 // this should not be set if the `collapse` key is removed from nbformat
1278 // this should not be set if the `collapse` key is removed from nbformat
1283 this.set_dirty(true);
1279 this.set_dirty(true);
1284 };
1280 };
1285
1281
1286 /**
1282 /**
1287 * Expand each code cell's output area, and add a scrollbar for long output.
1283 * Expand each code cell's output area, and add a scrollbar for long output.
1288 *
1284 *
1289 * @method scroll_all_output
1285 * @method scroll_all_output
1290 */
1286 */
1291 Notebook.prototype.scroll_all_output = function () {
1287 Notebook.prototype.scroll_all_output = function () {
1292 var ncells = this.ncells();
1288 var ncells = this.ncells();
1293 var cells = this.get_cells();
1289 var cells = this.get_cells();
1294 for (var i=0; i<ncells; i++) {
1290 for (var i=0; i<ncells; i++) {
1295 if (cells[i] instanceof IPython.CodeCell) {
1291 if (cells[i] instanceof IPython.CodeCell) {
1296 cells[i].output_area.expand();
1292 cells[i].output_area.expand();
1297 cells[i].output_area.scroll_if_long();
1293 cells[i].output_area.scroll_if_long();
1298 }
1294 }
1299 };
1295 };
1300 // this should not be set if the `collapse` key is removed from nbformat
1296 // this should not be set if the `collapse` key is removed from nbformat
1301 this.set_dirty(true);
1297 this.set_dirty(true);
1302 };
1298 };
1303
1299
1304 /**
1300 /**
1305 * Expand each code cell's output area, and remove scrollbars.
1301 * Expand each code cell's output area, and remove scrollbars.
1306 *
1302 *
1307 * @method expand_all_output
1303 * @method expand_all_output
1308 */
1304 */
1309 Notebook.prototype.expand_all_output = function () {
1305 Notebook.prototype.expand_all_output = function () {
1310 var ncells = this.ncells();
1306 var ncells = this.ncells();
1311 var cells = this.get_cells();
1307 var cells = this.get_cells();
1312 for (var i=0; i<ncells; i++) {
1308 for (var i=0; i<ncells; i++) {
1313 if (cells[i] instanceof IPython.CodeCell) {
1309 if (cells[i] instanceof IPython.CodeCell) {
1314 cells[i].output_area.expand();
1310 cells[i].output_area.expand();
1315 cells[i].output_area.unscroll_area();
1311 cells[i].output_area.unscroll_area();
1316 }
1312 }
1317 };
1313 };
1318 // this should not be set if the `collapse` key is removed from nbformat
1314 // this should not be set if the `collapse` key is removed from nbformat
1319 this.set_dirty(true);
1315 this.set_dirty(true);
1320 };
1316 };
1321
1317
1322 /**
1318 /**
1323 * Clear each code cell's output area.
1319 * Clear each code cell's output area.
1324 *
1320 *
1325 * @method clear_all_output
1321 * @method clear_all_output
1326 */
1322 */
1327 Notebook.prototype.clear_all_output = function () {
1323 Notebook.prototype.clear_all_output = function () {
1328 var ncells = this.ncells();
1324 var ncells = this.ncells();
1329 var cells = this.get_cells();
1325 var cells = this.get_cells();
1330 for (var i=0; i<ncells; i++) {
1326 for (var i=0; i<ncells; i++) {
1331 if (cells[i] instanceof IPython.CodeCell) {
1327 if (cells[i] instanceof IPython.CodeCell) {
1332 cells[i].clear_output(true,true,true);
1328 cells[i].clear_output(true,true,true);
1333 // Make all In[] prompts blank, as well
1329 // Make all In[] prompts blank, as well
1334 // TODO: make this configurable (via checkbox?)
1330 // TODO: make this configurable (via checkbox?)
1335 cells[i].set_input_prompt();
1331 cells[i].set_input_prompt();
1336 }
1332 }
1337 };
1333 };
1338 this.set_dirty(true);
1334 this.set_dirty(true);
1339 };
1335 };
1340
1336
1341
1337
1342 // Other cell functions: line numbers, ...
1338 // Other cell functions: line numbers, ...
1343
1339
1344 /**
1340 /**
1345 * Toggle line numbers in the selected cell's input area.
1341 * Toggle line numbers in the selected cell's input area.
1346 *
1342 *
1347 * @method cell_toggle_line_numbers
1343 * @method cell_toggle_line_numbers
1348 */
1344 */
1349 Notebook.prototype.cell_toggle_line_numbers = function() {
1345 Notebook.prototype.cell_toggle_line_numbers = function() {
1350 this.get_selected_cell().toggle_line_numbers();
1346 this.get_selected_cell().toggle_line_numbers();
1351 };
1347 };
1352
1348
1353 // Kernel related things
1349 // Kernel related things
1354
1350
1355 /**
1351 /**
1356 * Start a new kernel and set it on each code cell.
1352 * Start a new kernel and set it on each code cell.
1357 *
1353 *
1358 * @method start_kernel
1354 * @method start_kernel
1359 */
1355 */
1360 Notebook.prototype.start_kernel = function () {
1356 Notebook.prototype.start_kernel = function () {
1361 var base_url = $('body').data('baseKernelUrl') + "kernels";
1357 var base_url = $('body').data('baseKernelUrl') + "kernels";
1362 this.kernel = new IPython.Kernel(base_url);
1358 this.kernel = new IPython.Kernel(base_url);
1363 this.kernel.start(this.notebook_id);
1359 this.kernel.start(this.notebook_id);
1364 // Now that the kernel has been created, tell the CodeCells about it.
1360 // Now that the kernel has been created, tell the CodeCells about it.
1365 var ncells = this.ncells();
1361 var ncells = this.ncells();
1366 for (var i=0; i<ncells; i++) {
1362 for (var i=0; i<ncells; i++) {
1367 var cell = this.get_cell(i);
1363 var cell = this.get_cell(i);
1368 if (cell instanceof IPython.CodeCell) {
1364 if (cell instanceof IPython.CodeCell) {
1369 cell.set_kernel(this.kernel)
1365 cell.set_kernel(this.kernel)
1370 };
1366 };
1371 };
1367 };
1372 };
1368 };
1373
1369
1374 /**
1370 /**
1375 * Prompt the user to restart the IPython kernel.
1371 * Prompt the user to restart the IPython kernel.
1376 *
1372 *
1377 * @method restart_kernel
1373 * @method restart_kernel
1378 */
1374 */
1379 Notebook.prototype.restart_kernel = function () {
1375 Notebook.prototype.restart_kernel = function () {
1380 var that = this;
1376 var that = this;
1381 IPython.dialog.modal({
1377 IPython.dialog.modal({
1382 title : "Restart kernel or continue running?",
1378 title : "Restart kernel or continue running?",
1383 body : $("<p/>").html(
1379 body : $("<p/>").html(
1384 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1380 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1385 ),
1381 ),
1386 buttons : {
1382 buttons : {
1387 "Continue running" : {},
1383 "Continue running" : {},
1388 "Restart" : {
1384 "Restart" : {
1389 "class" : "btn-danger",
1385 "class" : "btn-danger",
1390 "click" : function() {
1386 "click" : function() {
1391 that.kernel.restart();
1387 that.kernel.restart();
1392 }
1388 }
1393 }
1389 }
1394 }
1390 }
1395 });
1391 });
1396 };
1392 };
1397
1393
1398 /**
1394 /**
1399 * Run the selected cell.
1395 * Run the selected cell.
1400 *
1396 *
1401 * Execute or render cell outputs.
1397 * Execute or render cell outputs.
1402 *
1398 *
1403 * @method execute_selected_cell
1399 * @method execute_selected_cell
1404 * @param {Object} options Customize post-execution behavior
1400 * @param {Object} options Customize post-execution behavior
1405 */
1401 */
1406 Notebook.prototype.execute_selected_cell = function (options) {
1402 Notebook.prototype.execute_selected_cell = function (options) {
1407 // add_new: should a new cell be added if we are at the end of the nb
1403 // add_new: should a new cell be added if we are at the end of the nb
1408 // terminal: execute in terminal mode, which stays in the current cell
1404 // terminal: execute in terminal mode, which stays in the current cell
1409 var default_options = {terminal: false, add_new: true};
1405 var default_options = {terminal: false, add_new: true};
1410 $.extend(default_options, options);
1406 $.extend(default_options, options);
1411 var that = this;
1407 var that = this;
1412 var cell = that.get_selected_cell();
1408 var cell = that.get_selected_cell();
1413 var cell_index = that.find_cell_index(cell);
1409 var cell_index = that.find_cell_index(cell);
1414 if (cell instanceof IPython.CodeCell) {
1410 if (cell instanceof IPython.CodeCell) {
1415 cell.execute();
1411 cell.execute();
1416 }
1412 }
1417 if (default_options.terminal) {
1413 if (default_options.terminal) {
1418 cell.select_all();
1414 cell.select_all();
1419 } else {
1415 } else {
1420 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1416 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1421 that.insert_cell_below('code');
1417 that.insert_cell_below('code');
1422 // If we are adding a new cell at the end, scroll down to show it.
1418 // If we are adding a new cell at the end, scroll down to show it.
1423 that.scroll_to_bottom();
1419 that.scroll_to_bottom();
1424 } else {
1420 } else {
1425 that.select(cell_index+1);
1421 that.select(cell_index+1);
1426 };
1422 };
1427 };
1423 };
1428 this.set_dirty(true);
1424 this.set_dirty(true);
1429 };
1425 };
1430
1426
1431 /**
1427 /**
1432 * Execute all cells below the selected cell.
1428 * Execute all cells below the selected cell.
1433 *
1429 *
1434 * @method execute_cells_below
1430 * @method execute_cells_below
1435 */
1431 */
1436 Notebook.prototype.execute_cells_below = function () {
1432 Notebook.prototype.execute_cells_below = function () {
1437 this.execute_cell_range(this.get_selected_index(), this.ncells());
1433 this.execute_cell_range(this.get_selected_index(), this.ncells());
1438 this.scroll_to_bottom();
1434 this.scroll_to_bottom();
1439 };
1435 };
1440
1436
1441 /**
1437 /**
1442 * Execute all cells above the selected cell.
1438 * Execute all cells above the selected cell.
1443 *
1439 *
1444 * @method execute_cells_above
1440 * @method execute_cells_above
1445 */
1441 */
1446 Notebook.prototype.execute_cells_above = function () {
1442 Notebook.prototype.execute_cells_above = function () {
1447 this.execute_cell_range(0, this.get_selected_index());
1443 this.execute_cell_range(0, this.get_selected_index());
1448 };
1444 };
1449
1445
1450 /**
1446 /**
1451 * Execute all cells.
1447 * Execute all cells.
1452 *
1448 *
1453 * @method execute_all_cells
1449 * @method execute_all_cells
1454 */
1450 */
1455 Notebook.prototype.execute_all_cells = function () {
1451 Notebook.prototype.execute_all_cells = function () {
1456 this.execute_cell_range(0, this.ncells());
1452 this.execute_cell_range(0, this.ncells());
1457 this.scroll_to_bottom();
1453 this.scroll_to_bottom();
1458 };
1454 };
1459
1455
1460 /**
1456 /**
1461 * Execute a contiguous range of cells.
1457 * Execute a contiguous range of cells.
1462 *
1458 *
1463 * @method execute_cell_range
1459 * @method execute_cell_range
1464 * @param {Number} start Index of the first cell to execute (inclusive)
1460 * @param {Number} start Index of the first cell to execute (inclusive)
1465 * @param {Number} end Index of the last cell to execute (exclusive)
1461 * @param {Number} end Index of the last cell to execute (exclusive)
1466 */
1462 */
1467 Notebook.prototype.execute_cell_range = function (start, end) {
1463 Notebook.prototype.execute_cell_range = function (start, end) {
1468 for (var i=start; i<end; i++) {
1464 for (var i=start; i<end; i++) {
1469 this.select(i);
1465 this.select(i);
1470 this.execute_selected_cell({add_new:false});
1466 this.execute_selected_cell({add_new:false});
1471 };
1467 };
1472 };
1468 };
1473
1469
1474 // Persistance and loading
1470 // Persistance and loading
1475
1471
1476 /**
1472 /**
1477 * Getter method for this notebook's ID.
1473 * Getter method for this notebook's ID.
1478 *
1474 *
1479 * @method get_notebook_id
1475 * @method get_notebook_id
1480 * @return {String} This notebook's ID
1476 * @return {String} This notebook's ID
1481 */
1477 */
1482 Notebook.prototype.get_notebook_id = function () {
1478 Notebook.prototype.get_notebook_id = function () {
1483 return this.notebook_id;
1479 return this.notebook_id;
1484 };
1480 };
1485
1481
1486 /**
1482 /**
1487 * Getter method for this notebook's name.
1483 * Getter method for this notebook's name.
1488 *
1484 *
1489 * @method get_notebook_name
1485 * @method get_notebook_name
1490 * @return {String} This notebook's name
1486 * @return {String} This notebook's name
1491 */
1487 */
1492 Notebook.prototype.get_notebook_name = function () {
1488 Notebook.prototype.get_notebook_name = function () {
1493 return this.notebook_name;
1489 return this.notebook_name;
1494 };
1490 };
1495
1491
1496 /**
1492 /**
1497 * Setter method for this notebook's name.
1493 * Setter method for this notebook's name.
1498 *
1494 *
1499 * @method set_notebook_name
1495 * @method set_notebook_name
1500 * @param {String} name A new name for this notebook
1496 * @param {String} name A new name for this notebook
1501 */
1497 */
1502 Notebook.prototype.set_notebook_name = function (name) {
1498 Notebook.prototype.set_notebook_name = function (name) {
1503 this.notebook_name = name;
1499 this.notebook_name = name;
1504 };
1500 };
1505
1501
1506 /**
1502 /**
1507 * Check that a notebook's name is valid.
1503 * Check that a notebook's name is valid.
1508 *
1504 *
1509 * @method test_notebook_name
1505 * @method test_notebook_name
1510 * @param {String} nbname A name for this notebook
1506 * @param {String} nbname A name for this notebook
1511 * @return {Boolean} True if the name is valid, false if invalid
1507 * @return {Boolean} True if the name is valid, false if invalid
1512 */
1508 */
1513 Notebook.prototype.test_notebook_name = function (nbname) {
1509 Notebook.prototype.test_notebook_name = function (nbname) {
1514 nbname = nbname || '';
1510 nbname = nbname || '';
1515 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1511 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1516 return true;
1512 return true;
1517 } else {
1513 } else {
1518 return false;
1514 return false;
1519 };
1515 };
1520 };
1516 };
1521
1517
1522 /**
1518 /**
1523 * Load a notebook from JSON (.ipynb).
1519 * Load a notebook from JSON (.ipynb).
1524 *
1520 *
1525 * This currently handles one worksheet: others are deleted.
1521 * This currently handles one worksheet: others are deleted.
1526 *
1522 *
1527 * @method fromJSON
1523 * @method fromJSON
1528 * @param {Object} data JSON representation of a notebook
1524 * @param {Object} data JSON representation of a notebook
1529 */
1525 */
1530 Notebook.prototype.fromJSON = function (data) {
1526 Notebook.prototype.fromJSON = function (data) {
1531 var ncells = this.ncells();
1527 var ncells = this.ncells();
1532 var i;
1528 var i;
1533 for (i=0; i<ncells; i++) {
1529 for (i=0; i<ncells; i++) {
1534 // Always delete cell 0 as they get renumbered as they are deleted.
1530 // Always delete cell 0 as they get renumbered as they are deleted.
1535 this.delete_cell(0);
1531 this.delete_cell(0);
1536 };
1532 };
1537 // Save the metadata and name.
1533 // Save the metadata and name.
1538 this.metadata = data.metadata;
1534 this.metadata = data.metadata;
1539 this.notebook_name = data.metadata.name;
1535 this.notebook_name = data.metadata.name;
1540 // Only handle 1 worksheet for now.
1536 // Only handle 1 worksheet for now.
1541 var worksheet = data.worksheets[0];
1537 var worksheet = data.worksheets[0];
1542 if (worksheet !== undefined) {
1538 if (worksheet !== undefined) {
1543 if (worksheet.metadata) {
1539 if (worksheet.metadata) {
1544 this.worksheet_metadata = worksheet.metadata;
1540 this.worksheet_metadata = worksheet.metadata;
1545 }
1541 }
1546 var new_cells = worksheet.cells;
1542 var new_cells = worksheet.cells;
1547 ncells = new_cells.length;
1543 ncells = new_cells.length;
1548 var cell_data = null;
1544 var cell_data = null;
1549 var new_cell = null;
1545 var new_cell = null;
1550 for (i=0; i<ncells; i++) {
1546 for (i=0; i<ncells; i++) {
1551 cell_data = new_cells[i];
1547 cell_data = new_cells[i];
1552 // VERSIONHACK: plaintext -> raw
1548 // VERSIONHACK: plaintext -> raw
1553 // handle never-released plaintext name for raw cells
1549 // handle never-released plaintext name for raw cells
1554 if (cell_data.cell_type === 'plaintext'){
1550 if (cell_data.cell_type === 'plaintext'){
1555 cell_data.cell_type = 'raw';
1551 cell_data.cell_type = 'raw';
1556 }
1552 }
1557
1553
1558 new_cell = this.insert_cell_below(cell_data.cell_type);
1554 new_cell = this.insert_cell_below(cell_data.cell_type);
1559 new_cell.fromJSON(cell_data);
1555 new_cell.fromJSON(cell_data);
1560 };
1556 };
1561 };
1557 };
1562 if (data.worksheets.length > 1) {
1558 if (data.worksheets.length > 1) {
1563 IPython.dialog.modal({
1559 IPython.dialog.modal({
1564 title : "Multiple worksheets",
1560 title : "Multiple worksheets",
1565 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1561 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1566 "but this version of IPython can only handle the first. " +
1562 "but this version of IPython can only handle the first. " +
1567 "If you save this notebook, worksheets after the first will be lost.",
1563 "If you save this notebook, worksheets after the first will be lost.",
1568 buttons : {
1564 buttons : {
1569 OK : {
1565 OK : {
1570 class : "btn-danger"
1566 class : "btn-danger"
1571 }
1567 }
1572 }
1568 }
1573 });
1569 });
1574 }
1570 }
1575 };
1571 };
1576
1572
1577 /**
1573 /**
1578 * Dump this notebook into a JSON-friendly object.
1574 * Dump this notebook into a JSON-friendly object.
1579 *
1575 *
1580 * @method toJSON
1576 * @method toJSON
1581 * @return {Object} A JSON-friendly representation of this notebook.
1577 * @return {Object} A JSON-friendly representation of this notebook.
1582 */
1578 */
1583 Notebook.prototype.toJSON = function () {
1579 Notebook.prototype.toJSON = function () {
1584 var cells = this.get_cells();
1580 var cells = this.get_cells();
1585 var ncells = cells.length;
1581 var ncells = cells.length;
1586 var cell_array = new Array(ncells);
1582 var cell_array = new Array(ncells);
1587 for (var i=0; i<ncells; i++) {
1583 for (var i=0; i<ncells; i++) {
1588 cell_array[i] = cells[i].toJSON();
1584 cell_array[i] = cells[i].toJSON();
1589 };
1585 };
1590 var data = {
1586 var data = {
1591 // Only handle 1 worksheet for now.
1587 // Only handle 1 worksheet for now.
1592 worksheets : [{
1588 worksheets : [{
1593 cells: cell_array,
1589 cells: cell_array,
1594 metadata: this.worksheet_metadata
1590 metadata: this.worksheet_metadata
1595 }],
1591 }],
1596 metadata : this.metadata
1592 metadata : this.metadata
1597 };
1593 };
1598 return data;
1594 return data;
1599 };
1595 };
1600
1596
1601 /**
1597 /**
1602 * Start an autosave timer, for periodically saving the notebook.
1598 * Start an autosave timer, for periodically saving the notebook.
1603 *
1599 *
1604 * @method set_autosave_interval
1600 * @method set_autosave_interval
1605 * @param {Integer} interval the autosave interval in milliseconds
1601 * @param {Integer} interval the autosave interval in milliseconds
1606 */
1602 */
1607 Notebook.prototype.set_autosave_interval = function (interval) {
1603 Notebook.prototype.set_autosave_interval = function (interval) {
1608 var that = this;
1604 var that = this;
1609 // clear previous interval, so we don't get simultaneous timers
1605 // clear previous interval, so we don't get simultaneous timers
1610 if (this.autosave_timer) {
1606 if (this.autosave_timer) {
1611 clearInterval(this.autosave_timer);
1607 clearInterval(this.autosave_timer);
1612 }
1608 }
1613
1609
1614 this.autosave_interval = this.minimum_autosave_interval = interval;
1610 this.autosave_interval = this.minimum_autosave_interval = interval;
1615 if (interval) {
1611 if (interval) {
1616 this.autosave_timer = setInterval(function() {
1612 this.autosave_timer = setInterval(function() {
1617 if (that.dirty) {
1613 if (that.dirty) {
1618 that.save_notebook();
1614 that.save_notebook();
1619 }
1615 }
1620 }, interval);
1616 }, interval);
1621 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1617 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1622 } else {
1618 } else {
1623 this.autosave_timer = null;
1619 this.autosave_timer = null;
1624 $([IPython.events]).trigger("autosave_disabled.Notebook");
1620 $([IPython.events]).trigger("autosave_disabled.Notebook");
1625 };
1621 };
1626 };
1622 };
1627
1623
1628 /**
1624 /**
1629 * Save this notebook on the server.
1625 * Save this notebook on the server.
1630 *
1626 *
1631 * @method save_notebook
1627 * @method save_notebook
1632 */
1628 */
1633 Notebook.prototype.save_notebook = function () {
1629 Notebook.prototype.save_notebook = function () {
1634 // We may want to move the name/id/nbformat logic inside toJSON?
1630 // We may want to move the name/id/nbformat logic inside toJSON?
1635 var data = this.toJSON();
1631 var data = this.toJSON();
1636 data.metadata.name = this.notebook_name;
1632 data.metadata.name = this.notebook_name;
1637 data.nbformat = this.nbformat;
1633 data.nbformat = this.nbformat;
1638 data.nbformat_minor = this.nbformat_minor;
1634 data.nbformat_minor = this.nbformat_minor;
1639
1635
1640 // time the ajax call for autosave tuning purposes.
1636 // time the ajax call for autosave tuning purposes.
1641 var start = new Date().getTime();
1637 var start = new Date().getTime();
1642
1638
1643 // We do the call with settings so we can set cache to false.
1639 // We do the call with settings so we can set cache to false.
1644 var settings = {
1640 var settings = {
1645 processData : false,
1641 processData : false,
1646 cache : false,
1642 cache : false,
1647 type : "PUT",
1643 type : "PUT",
1648 data : JSON.stringify(data),
1644 data : JSON.stringify(data),
1649 headers : {'Content-Type': 'application/json'},
1645 headers : {'Content-Type': 'application/json'},
1650 success : $.proxy(this.save_notebook_success, this, start),
1646 success : $.proxy(this.save_notebook_success, this, start),
1651 error : $.proxy(this.save_notebook_error, this)
1647 error : $.proxy(this.save_notebook_error, this)
1652 };
1648 };
1653 $([IPython.events]).trigger('notebook_saving.Notebook');
1649 $([IPython.events]).trigger('notebook_saving.Notebook');
1654 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1650 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1655 $.ajax(url, settings);
1651 $.ajax(url, settings);
1656 };
1652 };
1657
1653
1658 /**
1654 /**
1659 * Success callback for saving a notebook.
1655 * Success callback for saving a notebook.
1660 *
1656 *
1661 * @method save_notebook_success
1657 * @method save_notebook_success
1662 * @param {Integer} start the time when the save request started
1658 * @param {Integer} start the time when the save request started
1663 * @param {Object} data JSON representation of a notebook
1659 * @param {Object} data JSON representation of a notebook
1664 * @param {String} status Description of response status
1660 * @param {String} status Description of response status
1665 * @param {jqXHR} xhr jQuery Ajax object
1661 * @param {jqXHR} xhr jQuery Ajax object
1666 */
1662 */
1667 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1663 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1668 this.set_dirty(false);
1664 this.set_dirty(false);
1669 $([IPython.events]).trigger('notebook_saved.Notebook');
1665 $([IPython.events]).trigger('notebook_saved.Notebook');
1670 this._update_autosave_interval(start);
1666 this._update_autosave_interval(start);
1671 if (this._checkpoint_after_save) {
1667 if (this._checkpoint_after_save) {
1672 this.create_checkpoint();
1668 this.create_checkpoint();
1673 this._checkpoint_after_save = false;
1669 this._checkpoint_after_save = false;
1674 };
1670 };
1675 };
1671 };
1676
1672
1677 /**
1673 /**
1678 * update the autosave interval based on how long the last save took
1674 * update the autosave interval based on how long the last save took
1679 *
1675 *
1680 * @method _update_autosave_interval
1676 * @method _update_autosave_interval
1681 * @param {Integer} timestamp when the save request started
1677 * @param {Integer} timestamp when the save request started
1682 */
1678 */
1683 Notebook.prototype._update_autosave_interval = function (start) {
1679 Notebook.prototype._update_autosave_interval = function (start) {
1684 var duration = (new Date().getTime() - start);
1680 var duration = (new Date().getTime() - start);
1685 if (this.autosave_interval) {
1681 if (this.autosave_interval) {
1686 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1682 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1687 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1683 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1688 // round to 10 seconds, otherwise we will be setting a new interval too often
1684 // round to 10 seconds, otherwise we will be setting a new interval too often
1689 interval = 10000 * Math.round(interval / 10000);
1685 interval = 10000 * Math.round(interval / 10000);
1690 // set new interval, if it's changed
1686 // set new interval, if it's changed
1691 if (interval != this.autosave_interval) {
1687 if (interval != this.autosave_interval) {
1692 this.set_autosave_interval(interval);
1688 this.set_autosave_interval(interval);
1693 }
1689 }
1694 }
1690 }
1695 };
1691 };
1696
1692
1697 /**
1693 /**
1698 * Failure callback for saving a notebook.
1694 * Failure callback for saving a notebook.
1699 *
1695 *
1700 * @method save_notebook_error
1696 * @method save_notebook_error
1701 * @param {jqXHR} xhr jQuery Ajax object
1697 * @param {jqXHR} xhr jQuery Ajax object
1702 * @param {String} status Description of response status
1698 * @param {String} status Description of response status
1703 * @param {String} error_msg HTTP error message
1699 * @param {String} error_msg HTTP error message
1704 */
1700 */
1705 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1701 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1706 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1702 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1707 };
1703 };
1708
1704
1709 /**
1705 /**
1710 * Request a notebook's data from the server.
1706 * Request a notebook's data from the server.
1711 *
1707 *
1712 * @method load_notebook
1708 * @method load_notebook
1713 * @param {String} notebook_id A notebook to load
1709 * @param {String} notebook_id A notebook to load
1714 */
1710 */
1715 Notebook.prototype.load_notebook = function (notebook_id) {
1711 Notebook.prototype.load_notebook = function (notebook_id) {
1716 var that = this;
1712 var that = this;
1717 this.notebook_id = notebook_id;
1713 this.notebook_id = notebook_id;
1718 // We do the call with settings so we can set cache to false.
1714 // We do the call with settings so we can set cache to false.
1719 var settings = {
1715 var settings = {
1720 processData : false,
1716 processData : false,
1721 cache : false,
1717 cache : false,
1722 type : "GET",
1718 type : "GET",
1723 dataType : "json",
1719 dataType : "json",
1724 success : $.proxy(this.load_notebook_success,this),
1720 success : $.proxy(this.load_notebook_success,this),
1725 error : $.proxy(this.load_notebook_error,this),
1721 error : $.proxy(this.load_notebook_error,this),
1726 };
1722 };
1727 $([IPython.events]).trigger('notebook_loading.Notebook');
1723 $([IPython.events]).trigger('notebook_loading.Notebook');
1728 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1724 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1729 $.ajax(url, settings);
1725 $.ajax(url, settings);
1730 };
1726 };
1731
1727
1732 /**
1728 /**
1733 * Success callback for loading a notebook from the server.
1729 * Success callback for loading a notebook from the server.
1734 *
1730 *
1735 * Load notebook data from the JSON response.
1731 * Load notebook data from the JSON response.
1736 *
1732 *
1737 * @method load_notebook_success
1733 * @method load_notebook_success
1738 * @param {Object} data JSON representation of a notebook
1734 * @param {Object} data JSON representation of a notebook
1739 * @param {String} status Description of response status
1735 * @param {String} status Description of response status
1740 * @param {jqXHR} xhr jQuery Ajax object
1736 * @param {jqXHR} xhr jQuery Ajax object
1741 */
1737 */
1742 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1738 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1743 this.fromJSON(data);
1739 this.fromJSON(data);
1744 if (this.ncells() === 0) {
1740 if (this.ncells() === 0) {
1745 this.insert_cell_below('code');
1741 this.insert_cell_below('code');
1746 };
1742 };
1747 this.set_dirty(false);
1743 this.set_dirty(false);
1748 this.select(0);
1744 this.select(0);
1749 this.scroll_to_top();
1745 this.scroll_to_top();
1750 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1746 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1751 var msg = "This notebook has been converted from an older " +
1747 var msg = "This notebook has been converted from an older " +
1752 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1748 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1753 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1749 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1754 "newer notebook format will be used and older versions of IPython " +
1750 "newer notebook format will be used and older versions of IPython " +
1755 "may not be able to read it. To keep the older version, close the " +
1751 "may not be able to read it. To keep the older version, close the " +
1756 "notebook without saving it.";
1752 "notebook without saving it.";
1757 IPython.dialog.modal({
1753 IPython.dialog.modal({
1758 title : "Notebook converted",
1754 title : "Notebook converted",
1759 body : msg,
1755 body : msg,
1760 buttons : {
1756 buttons : {
1761 OK : {
1757 OK : {
1762 class : "btn-primary"
1758 class : "btn-primary"
1763 }
1759 }
1764 }
1760 }
1765 });
1761 });
1766 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1762 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1767 var that = this;
1763 var that = this;
1768 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1764 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1769 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1765 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1770 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1766 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1771 this_vs + ". You can still work with this notebook, but some features " +
1767 this_vs + ". You can still work with this notebook, but some features " +
1772 "introduced in later notebook versions may not be available."
1768 "introduced in later notebook versions may not be available."
1773
1769
1774 IPython.dialog.modal({
1770 IPython.dialog.modal({
1775 title : "Newer Notebook",
1771 title : "Newer Notebook",
1776 body : msg,
1772 body : msg,
1777 buttons : {
1773 buttons : {
1778 OK : {
1774 OK : {
1779 class : "btn-danger"
1775 class : "btn-danger"
1780 }
1776 }
1781 }
1777 }
1782 });
1778 });
1783
1779
1784 }
1780 }
1785
1781
1786 // Create the kernel after the notebook is completely loaded to prevent
1782 // Create the kernel after the notebook is completely loaded to prevent
1787 // code execution upon loading, which is a security risk.
1783 // code execution upon loading, which is a security risk.
1788 if (! this.read_only) {
1784 this.start_kernel();
1789 this.start_kernel();
1785 // load our checkpoint list
1790 // load our checkpoint list
1786 IPython.notebook.list_checkpoints();
1791 IPython.notebook.list_checkpoints();
1787
1792 }
1793 $([IPython.events]).trigger('notebook_loaded.Notebook');
1788 $([IPython.events]).trigger('notebook_loaded.Notebook');
1794 };
1789 };
1795
1790
1796 /**
1791 /**
1797 * Failure callback for loading a notebook from the server.
1792 * Failure callback for loading a notebook from the server.
1798 *
1793 *
1799 * @method load_notebook_error
1794 * @method load_notebook_error
1800 * @param {jqXHR} xhr jQuery Ajax object
1795 * @param {jqXHR} xhr jQuery Ajax object
1801 * @param {String} textStatus Description of response status
1796 * @param {String} textStatus Description of response status
1802 * @param {String} errorThrow HTTP error message
1797 * @param {String} errorThrow HTTP error message
1803 */
1798 */
1804 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1799 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1805 if (xhr.status === 400) {
1800 if (xhr.status === 400) {
1806 var msg = errorThrow;
1801 var msg = errorThrow;
1807 } else if (xhr.status === 500) {
1802 } else if (xhr.status === 500) {
1808 var msg = "An unknown error occurred while loading this notebook. " +
1803 var msg = "An unknown error occurred while loading this notebook. " +
1809 "This version can load notebook formats " +
1804 "This version can load notebook formats " +
1810 "v" + this.nbformat + " or earlier.";
1805 "v" + this.nbformat + " or earlier.";
1811 }
1806 }
1812 IPython.dialog.modal({
1807 IPython.dialog.modal({
1813 title: "Error loading notebook",
1808 title: "Error loading notebook",
1814 body : msg,
1809 body : msg,
1815 buttons : {
1810 buttons : {
1816 "OK": {}
1811 "OK": {}
1817 }
1812 }
1818 });
1813 });
1819 }
1814 }
1820
1815
1821 /********************* checkpoint-related *********************/
1816 /********************* checkpoint-related *********************/
1822
1817
1823 /**
1818 /**
1824 * Save the notebook then immediately create a checkpoint.
1819 * Save the notebook then immediately create a checkpoint.
1825 *
1820 *
1826 * @method save_checkpoint
1821 * @method save_checkpoint
1827 */
1822 */
1828 Notebook.prototype.save_checkpoint = function () {
1823 Notebook.prototype.save_checkpoint = function () {
1829 this._checkpoint_after_save = true;
1824 this._checkpoint_after_save = true;
1830 this.save_notebook();
1825 this.save_notebook();
1831 };
1826 };
1832
1827
1833 /**
1828 /**
1834 * List checkpoints for this notebook.
1829 * List checkpoints for this notebook.
1835 *
1830 *
1836 * @method list_checkpoint
1831 * @method list_checkpoint
1837 */
1832 */
1838 Notebook.prototype.list_checkpoints = function () {
1833 Notebook.prototype.list_checkpoints = function () {
1839 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1834 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1840 $.get(url).done(
1835 $.get(url).done(
1841 $.proxy(this.list_checkpoints_success, this)
1836 $.proxy(this.list_checkpoints_success, this)
1842 ).fail(
1837 ).fail(
1843 $.proxy(this.list_checkpoints_error, this)
1838 $.proxy(this.list_checkpoints_error, this)
1844 );
1839 );
1845 };
1840 };
1846
1841
1847 /**
1842 /**
1848 * Success callback for listing checkpoints.
1843 * Success callback for listing checkpoints.
1849 *
1844 *
1850 * @method list_checkpoint_success
1845 * @method list_checkpoint_success
1851 * @param {Object} data JSON representation of a checkpoint
1846 * @param {Object} data JSON representation of a checkpoint
1852 * @param {String} status Description of response status
1847 * @param {String} status Description of response status
1853 * @param {jqXHR} xhr jQuery Ajax object
1848 * @param {jqXHR} xhr jQuery Ajax object
1854 */
1849 */
1855 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1850 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1856 var data = $.parseJSON(data);
1851 var data = $.parseJSON(data);
1857 if (data.length) {
1852 if (data.length) {
1858 this.last_checkpoint = data[0];
1853 this.last_checkpoint = data[0];
1859 } else {
1854 } else {
1860 this.last_checkpoint = null;
1855 this.last_checkpoint = null;
1861 }
1856 }
1862 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1857 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1863 };
1858 };
1864
1859
1865 /**
1860 /**
1866 * Failure callback for listing a checkpoint.
1861 * Failure callback for listing a checkpoint.
1867 *
1862 *
1868 * @method list_checkpoint_error
1863 * @method list_checkpoint_error
1869 * @param {jqXHR} xhr jQuery Ajax object
1864 * @param {jqXHR} xhr jQuery Ajax object
1870 * @param {String} status Description of response status
1865 * @param {String} status Description of response status
1871 * @param {String} error_msg HTTP error message
1866 * @param {String} error_msg HTTP error message
1872 */
1867 */
1873 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1868 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1874 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1869 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1875 };
1870 };
1876
1871
1877 /**
1872 /**
1878 * Create a checkpoint of this notebook on the server from the most recent save.
1873 * Create a checkpoint of this notebook on the server from the most recent save.
1879 *
1874 *
1880 * @method create_checkpoint
1875 * @method create_checkpoint
1881 */
1876 */
1882 Notebook.prototype.create_checkpoint = function () {
1877 Notebook.prototype.create_checkpoint = function () {
1883 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1878 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1884 $.post(url).done(
1879 $.post(url).done(
1885 $.proxy(this.create_checkpoint_success, this)
1880 $.proxy(this.create_checkpoint_success, this)
1886 ).fail(
1881 ).fail(
1887 $.proxy(this.create_checkpoint_error, this)
1882 $.proxy(this.create_checkpoint_error, this)
1888 );
1883 );
1889 };
1884 };
1890
1885
1891 /**
1886 /**
1892 * Success callback for creating a checkpoint.
1887 * Success callback for creating a checkpoint.
1893 *
1888 *
1894 * @method create_checkpoint_success
1889 * @method create_checkpoint_success
1895 * @param {Object} data JSON representation of a checkpoint
1890 * @param {Object} data JSON representation of a checkpoint
1896 * @param {String} status Description of response status
1891 * @param {String} status Description of response status
1897 * @param {jqXHR} xhr jQuery Ajax object
1892 * @param {jqXHR} xhr jQuery Ajax object
1898 */
1893 */
1899 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1894 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1900 var data = $.parseJSON(data);
1895 var data = $.parseJSON(data);
1901 this.last_checkpoint = data;
1896 this.last_checkpoint = data;
1902 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1897 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1903 };
1898 };
1904
1899
1905 /**
1900 /**
1906 * Failure callback for creating a checkpoint.
1901 * Failure callback for creating a checkpoint.
1907 *
1902 *
1908 * @method create_checkpoint_error
1903 * @method create_checkpoint_error
1909 * @param {jqXHR} xhr jQuery Ajax object
1904 * @param {jqXHR} xhr jQuery Ajax object
1910 * @param {String} status Description of response status
1905 * @param {String} status Description of response status
1911 * @param {String} error_msg HTTP error message
1906 * @param {String} error_msg HTTP error message
1912 */
1907 */
1913 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1908 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1914 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1909 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1915 };
1910 };
1916
1911
1917 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1912 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1918 var that = this;
1913 var that = this;
1919 var checkpoint = checkpoint || this.last_checkpoint;
1914 var checkpoint = checkpoint || this.last_checkpoint;
1920 if ( ! checkpoint ) {
1915 if ( ! checkpoint ) {
1921 console.log("restore dialog, but no checkpoint to restore to!");
1916 console.log("restore dialog, but no checkpoint to restore to!");
1922 return;
1917 return;
1923 }
1918 }
1924 var body = $('<div/>').append(
1919 var body = $('<div/>').append(
1925 $('<p/>').addClass("p-space").text(
1920 $('<p/>').addClass("p-space").text(
1926 "Are you sure you want to revert the notebook to " +
1921 "Are you sure you want to revert the notebook to " +
1927 "the latest checkpoint?"
1922 "the latest checkpoint?"
1928 ).append(
1923 ).append(
1929 $("<strong/>").text(
1924 $("<strong/>").text(
1930 " This cannot be undone."
1925 " This cannot be undone."
1931 )
1926 )
1932 )
1927 )
1933 ).append(
1928 ).append(
1934 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1929 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1935 ).append(
1930 ).append(
1936 $('<p/>').addClass("p-space").text(
1931 $('<p/>').addClass("p-space").text(
1937 Date(checkpoint.last_modified)
1932 Date(checkpoint.last_modified)
1938 ).css("text-align", "center")
1933 ).css("text-align", "center")
1939 );
1934 );
1940
1935
1941 IPython.dialog.modal({
1936 IPython.dialog.modal({
1942 title : "Revert notebook to checkpoint",
1937 title : "Revert notebook to checkpoint",
1943 body : body,
1938 body : body,
1944 buttons : {
1939 buttons : {
1945 Revert : {
1940 Revert : {
1946 class : "btn-danger",
1941 class : "btn-danger",
1947 click : function () {
1942 click : function () {
1948 that.restore_checkpoint(checkpoint.checkpoint_id);
1943 that.restore_checkpoint(checkpoint.checkpoint_id);
1949 }
1944 }
1950 },
1945 },
1951 Cancel : {}
1946 Cancel : {}
1952 }
1947 }
1953 });
1948 });
1954 }
1949 }
1955
1950
1956 /**
1951 /**
1957 * Restore the notebook to a checkpoint state.
1952 * Restore the notebook to a checkpoint state.
1958 *
1953 *
1959 * @method restore_checkpoint
1954 * @method restore_checkpoint
1960 * @param {String} checkpoint ID
1955 * @param {String} checkpoint ID
1961 */
1956 */
1962 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1957 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1963 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1958 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1964 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1959 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1965 $.post(url).done(
1960 $.post(url).done(
1966 $.proxy(this.restore_checkpoint_success, this)
1961 $.proxy(this.restore_checkpoint_success, this)
1967 ).fail(
1962 ).fail(
1968 $.proxy(this.restore_checkpoint_error, this)
1963 $.proxy(this.restore_checkpoint_error, this)
1969 );
1964 );
1970 };
1965 };
1971
1966
1972 /**
1967 /**
1973 * Success callback for restoring a notebook to a checkpoint.
1968 * Success callback for restoring a notebook to a checkpoint.
1974 *
1969 *
1975 * @method restore_checkpoint_success
1970 * @method restore_checkpoint_success
1976 * @param {Object} data (ignored, should be empty)
1971 * @param {Object} data (ignored, should be empty)
1977 * @param {String} status Description of response status
1972 * @param {String} status Description of response status
1978 * @param {jqXHR} xhr jQuery Ajax object
1973 * @param {jqXHR} xhr jQuery Ajax object
1979 */
1974 */
1980 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
1975 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
1981 $([IPython.events]).trigger('checkpoint_restored.Notebook');
1976 $([IPython.events]).trigger('checkpoint_restored.Notebook');
1982 this.load_notebook(this.notebook_id);
1977 this.load_notebook(this.notebook_id);
1983 };
1978 };
1984
1979
1985 /**
1980 /**
1986 * Failure callback for restoring a notebook to a checkpoint.
1981 * Failure callback for restoring a notebook to a checkpoint.
1987 *
1982 *
1988 * @method restore_checkpoint_error
1983 * @method restore_checkpoint_error
1989 * @param {jqXHR} xhr jQuery Ajax object
1984 * @param {jqXHR} xhr jQuery Ajax object
1990 * @param {String} status Description of response status
1985 * @param {String} status Description of response status
1991 * @param {String} error_msg HTTP error message
1986 * @param {String} error_msg HTTP error message
1992 */
1987 */
1993 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
1988 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
1994 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
1989 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
1995 };
1990 };
1996
1991
1997 /**
1992 /**
1998 * Delete a notebook checkpoint.
1993 * Delete a notebook checkpoint.
1999 *
1994 *
2000 * @method delete_checkpoint
1995 * @method delete_checkpoint
2001 * @param {String} checkpoint ID
1996 * @param {String} checkpoint ID
2002 */
1997 */
2003 Notebook.prototype.delete_checkpoint = function (checkpoint) {
1998 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2004 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
1999 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
2005 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
2000 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
2006 $.ajax(url, {
2001 $.ajax(url, {
2007 type: 'DELETE',
2002 type: 'DELETE',
2008 success: $.proxy(this.delete_checkpoint_success, this),
2003 success: $.proxy(this.delete_checkpoint_success, this),
2009 error: $.proxy(this.delete_notebook_error,this)
2004 error: $.proxy(this.delete_notebook_error,this)
2010 });
2005 });
2011 };
2006 };
2012
2007
2013 /**
2008 /**
2014 * Success callback for deleting a notebook checkpoint
2009 * Success callback for deleting a notebook checkpoint
2015 *
2010 *
2016 * @method delete_checkpoint_success
2011 * @method delete_checkpoint_success
2017 * @param {Object} data (ignored, should be empty)
2012 * @param {Object} data (ignored, should be empty)
2018 * @param {String} status Description of response status
2013 * @param {String} status Description of response status
2019 * @param {jqXHR} xhr jQuery Ajax object
2014 * @param {jqXHR} xhr jQuery Ajax object
2020 */
2015 */
2021 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2016 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2022 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2017 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2023 this.load_notebook(this.notebook_id);
2018 this.load_notebook(this.notebook_id);
2024 };
2019 };
2025
2020
2026 /**
2021 /**
2027 * Failure callback for deleting a notebook checkpoint.
2022 * Failure callback for deleting a notebook checkpoint.
2028 *
2023 *
2029 * @method delete_checkpoint_error
2024 * @method delete_checkpoint_error
2030 * @param {jqXHR} xhr jQuery Ajax object
2025 * @param {jqXHR} xhr jQuery Ajax object
2031 * @param {String} status Description of response status
2026 * @param {String} status Description of response status
2032 * @param {String} error_msg HTTP error message
2027 * @param {String} error_msg HTTP error message
2033 */
2028 */
2034 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2029 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2035 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2030 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2036 };
2031 };
2037
2032
2038
2033
2039 IPython.Notebook = Notebook;
2034 IPython.Notebook = Notebook;
2040
2035
2041
2036
2042 return IPython;
2037 return IPython;
2043
2038
2044 }(IPython));
2039 }(IPython));
2045
2040
@@ -1,557 +1,556 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // TextCell
9 // TextCell
10 //============================================================================
10 //============================================================================
11
11
12
12
13
13
14 /**
14 /**
15 A module that allow to create different type of Text Cell
15 A module that allow to create different type of Text Cell
16 @module IPython
16 @module IPython
17 @namespace IPython
17 @namespace IPython
18 */
18 */
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20 "use strict";
21
21
22 // TextCell base class
22 // TextCell base class
23 var key = IPython.utils.keycodes;
23 var key = IPython.utils.keycodes;
24
24
25 /**
25 /**
26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
26 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
27 * cell start as not redered.
27 * cell start as not redered.
28 *
28 *
29 * @class TextCell
29 * @class TextCell
30 * @constructor TextCell
30 * @constructor TextCell
31 * @extend IPython.Cell
31 * @extend IPython.Cell
32 * @param {object|undefined} [options]
32 * @param {object|undefined} [options]
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
34 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
35 */
35 */
36 var TextCell = function (options) {
36 var TextCell = function (options) {
37 // in all TextCell/Cell subclasses
37 // in all TextCell/Cell subclasses
38 // do not assign most of members here, just pass it down
38 // do not assign most of members here, just pass it down
39 // in the options dict potentially overwriting what you wish.
39 // in the options dict potentially overwriting what you wish.
40 // they will be assigned in the base class.
40 // they will be assigned in the base class.
41
41
42 // we cannot put this as a class key as it has handle to "this".
42 // we cannot put this as a class key as it has handle to "this".
43 var cm_overwrite_options = {
43 var cm_overwrite_options = {
44 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
44 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
45 };
45 };
46
46
47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
48
48
49 IPython.Cell.apply(this, [options]);
49 IPython.Cell.apply(this, [options]);
50
50
51
51
52 this.rendered = false;
52 this.rendered = false;
53 this.cell_type = this.cell_type || 'text';
53 this.cell_type = this.cell_type || 'text';
54 };
54 };
55
55
56 TextCell.prototype = new IPython.Cell();
56 TextCell.prototype = new IPython.Cell();
57
57
58 TextCell.options_default = {
58 TextCell.options_default = {
59 cm_config : {
59 cm_config : {
60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
60 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
61 mode: 'htmlmixed',
61 mode: 'htmlmixed',
62 lineWrapping : true,
62 lineWrapping : true,
63 }
63 }
64 };
64 };
65
65
66
66
67
67
68 /**
68 /**
69 * Create the DOM element of the TextCell
69 * Create the DOM element of the TextCell
70 * @method create_element
70 * @method create_element
71 * @private
71 * @private
72 */
72 */
73 TextCell.prototype.create_element = function () {
73 TextCell.prototype.create_element = function () {
74 IPython.Cell.prototype.create_element.apply(this, arguments);
74 IPython.Cell.prototype.create_element.apply(this, arguments);
75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
75 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
76 cell.attr('tabindex','2');
76 cell.attr('tabindex','2');
77
77
78 this.celltoolbar = new IPython.CellToolbar(this);
78 this.celltoolbar = new IPython.CellToolbar(this);
79 cell.append(this.celltoolbar.element);
79 cell.append(this.celltoolbar.element);
80
80
81 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
81 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
82 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
82 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
83
83
84 // The tabindex=-1 makes this div focusable.
84 // The tabindex=-1 makes this div focusable.
85 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
85 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
86 addClass('rendered_html').attr('tabindex','-1');
86 addClass('rendered_html').attr('tabindex','-1');
87 cell.append(input_area).append(render_area);
87 cell.append(input_area).append(render_area);
88 this.element = cell;
88 this.element = cell;
89 };
89 };
90
90
91
91
92 /**
92 /**
93 * Bind the DOM evet to cell actions
93 * Bind the DOM evet to cell actions
94 * Need to be called after TextCell.create_element
94 * Need to be called after TextCell.create_element
95 * @private
95 * @private
96 * @method bind_event
96 * @method bind_event
97 */
97 */
98 TextCell.prototype.bind_events = function () {
98 TextCell.prototype.bind_events = function () {
99 IPython.Cell.prototype.bind_events.apply(this);
99 IPython.Cell.prototype.bind_events.apply(this);
100 var that = this;
100 var that = this;
101 this.element.keydown(function (event) {
101 this.element.keydown(function (event) {
102 if (event.which === 13 && !event.shiftKey) {
102 if (event.which === 13 && !event.shiftKey) {
103 if (that.rendered) {
103 if (that.rendered) {
104 that.edit();
104 that.edit();
105 return false;
105 return false;
106 };
106 };
107 };
107 };
108 });
108 });
109 this.element.dblclick(function () {
109 this.element.dblclick(function () {
110 that.edit();
110 that.edit();
111 });
111 });
112 };
112 };
113
113
114 /**
114 /**
115 * This method gets called in CodeMirror's onKeyDown/onKeyPress
115 * This method gets called in CodeMirror's onKeyDown/onKeyPress
116 * handlers and is used to provide custom key handling.
116 * handlers and is used to provide custom key handling.
117 *
117 *
118 * Subclass should override this method to have custom handeling
118 * Subclass should override this method to have custom handeling
119 *
119 *
120 * @method handle_codemirror_keyevent
120 * @method handle_codemirror_keyevent
121 * @param {CodeMirror} editor - The codemirror instance bound to the cell
121 * @param {CodeMirror} editor - The codemirror instance bound to the cell
122 * @param {event} event -
122 * @param {event} event -
123 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
123 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
124 */
124 */
125 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
125 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
126
126
127 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
127 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
128 // Always ignore shift-enter in CodeMirror as we handle it.
128 // Always ignore shift-enter in CodeMirror as we handle it.
129 return true;
129 return true;
130 }
130 }
131 return false;
131 return false;
132 };
132 };
133
133
134 /**
134 /**
135 * Select the current cell and trigger 'focus'
135 * Select the current cell and trigger 'focus'
136 * @method select
136 * @method select
137 */
137 */
138 TextCell.prototype.select = function () {
138 TextCell.prototype.select = function () {
139 IPython.Cell.prototype.select.apply(this);
139 IPython.Cell.prototype.select.apply(this);
140 var output = this.element.find("div.text_cell_render");
140 var output = this.element.find("div.text_cell_render");
141 output.trigger('focus');
141 output.trigger('focus');
142 };
142 };
143
143
144 /**
144 /**
145 * unselect the current cell and `render` it
145 * unselect the current cell and `render` it
146 * @method unselect
146 * @method unselect
147 */
147 */
148 TextCell.prototype.unselect = function() {
148 TextCell.prototype.unselect = function() {
149 // render on selection of another cell
149 // render on selection of another cell
150 this.render();
150 this.render();
151 IPython.Cell.prototype.unselect.apply(this);
151 IPython.Cell.prototype.unselect.apply(this);
152 };
152 };
153
153
154 /**
154 /**
155 *
155 *
156 * put the current cell in edition mode
156 * put the current cell in edition mode
157 * @method edit
157 * @method edit
158 */
158 */
159 TextCell.prototype.edit = function () {
159 TextCell.prototype.edit = function () {
160 if ( this.read_only ) return;
161 if (this.rendered === true) {
160 if (this.rendered === true) {
162 var text_cell = this.element;
161 var text_cell = this.element;
163 var output = text_cell.find("div.text_cell_render");
162 var output = text_cell.find("div.text_cell_render");
164 output.hide();
163 output.hide();
165 text_cell.find('div.text_cell_input').show();
164 text_cell.find('div.text_cell_input').show();
166 this.code_mirror.refresh();
165 this.code_mirror.refresh();
167 this.code_mirror.focus();
166 this.code_mirror.focus();
168 // We used to need an additional refresh() after the focus, but
167 // We used to need an additional refresh() after the focus, but
169 // it appears that this has been fixed in CM. This bug would show
168 // it appears that this has been fixed in CM. This bug would show
170 // up on FF when a newly loaded markdown cell was edited.
169 // up on FF when a newly loaded markdown cell was edited.
171 this.rendered = false;
170 this.rendered = false;
172 if (this.get_text() === this.placeholder) {
171 if (this.get_text() === this.placeholder) {
173 this.set_text('');
172 this.set_text('');
174 this.refresh();
173 this.refresh();
175 }
174 }
176 }
175 }
177 };
176 };
178
177
179
178
180 /**
179 /**
181 * Empty, Subclasses must define render.
180 * Empty, Subclasses must define render.
182 * @method render
181 * @method render
183 */
182 */
184 TextCell.prototype.render = function () {};
183 TextCell.prototype.render = function () {};
185
184
186
185
187 /**
186 /**
188 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
187 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
189 * @method get_text
188 * @method get_text
190 * @retrun {string} CodeMirror current text value
189 * @retrun {string} CodeMirror current text value
191 */
190 */
192 TextCell.prototype.get_text = function() {
191 TextCell.prototype.get_text = function() {
193 return this.code_mirror.getValue();
192 return this.code_mirror.getValue();
194 };
193 };
195
194
196 /**
195 /**
197 * @param {string} text - Codemiror text value
196 * @param {string} text - Codemiror text value
198 * @see TextCell#get_text
197 * @see TextCell#get_text
199 * @method set_text
198 * @method set_text
200 * */
199 * */
201 TextCell.prototype.set_text = function(text) {
200 TextCell.prototype.set_text = function(text) {
202 this.code_mirror.setValue(text);
201 this.code_mirror.setValue(text);
203 this.code_mirror.refresh();
202 this.code_mirror.refresh();
204 };
203 };
205
204
206 /**
205 /**
207 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
206 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
208 * @method get_rendered
207 * @method get_rendered
209 * @return {html} html of rendered element
208 * @return {html} html of rendered element
210 * */
209 * */
211 TextCell.prototype.get_rendered = function() {
210 TextCell.prototype.get_rendered = function() {
212 return this.element.find('div.text_cell_render').html();
211 return this.element.find('div.text_cell_render').html();
213 };
212 };
214
213
215 /**
214 /**
216 * @method set_rendered
215 * @method set_rendered
217 */
216 */
218 TextCell.prototype.set_rendered = function(text) {
217 TextCell.prototype.set_rendered = function(text) {
219 this.element.find('div.text_cell_render').html(text);
218 this.element.find('div.text_cell_render').html(text);
220 };
219 };
221
220
222 /**
221 /**
223 * not deprecated, but implementation wrong
222 * not deprecated, but implementation wrong
224 * @method at_top
223 * @method at_top
225 * @deprecated
224 * @deprecated
226 * @return {Boolean} true is cell rendered, false otherwise
225 * @return {Boolean} true is cell rendered, false otherwise
227 * I doubt this is what it is supposed to do
226 * I doubt this is what it is supposed to do
228 * this implementation is completly false
227 * this implementation is completly false
229 */
228 */
230 TextCell.prototype.at_top = function () {
229 TextCell.prototype.at_top = function () {
231 if (this.rendered) {
230 if (this.rendered) {
232 return true;
231 return true;
233 } else {
232 } else {
234 return false;
233 return false;
235 }
234 }
236 };
235 };
237
236
238
237
239 /**
238 /**
240 * not deprecated, but implementation wrong
239 * not deprecated, but implementation wrong
241 * @method at_bottom
240 * @method at_bottom
242 * @deprecated
241 * @deprecated
243 * @return {Boolean} true is cell rendered, false otherwise
242 * @return {Boolean} true is cell rendered, false otherwise
244 * I doubt this is what it is supposed to do
243 * I doubt this is what it is supposed to do
245 * this implementation is completly false
244 * this implementation is completly false
246 * */
245 * */
247 TextCell.prototype.at_bottom = function () {
246 TextCell.prototype.at_bottom = function () {
248 if (this.rendered) {
247 if (this.rendered) {
249 return true;
248 return true;
250 } else {
249 } else {
251 return false;
250 return false;
252 }
251 }
253 };
252 };
254
253
255 /**
254 /**
256 * Create Text cell from JSON
255 * Create Text cell from JSON
257 * @param {json} data - JSON serialized text-cell
256 * @param {json} data - JSON serialized text-cell
258 * @method fromJSON
257 * @method fromJSON
259 */
258 */
260 TextCell.prototype.fromJSON = function (data) {
259 TextCell.prototype.fromJSON = function (data) {
261 IPython.Cell.prototype.fromJSON.apply(this, arguments);
260 IPython.Cell.prototype.fromJSON.apply(this, arguments);
262 if (data.cell_type === this.cell_type) {
261 if (data.cell_type === this.cell_type) {
263 if (data.source !== undefined) {
262 if (data.source !== undefined) {
264 this.set_text(data.source);
263 this.set_text(data.source);
265 // make this value the starting point, so that we can only undo
264 // make this value the starting point, so that we can only undo
266 // to this state, instead of a blank cell
265 // to this state, instead of a blank cell
267 this.code_mirror.clearHistory();
266 this.code_mirror.clearHistory();
268 this.set_rendered(data.rendered || '');
267 this.set_rendered(data.rendered || '');
269 this.rendered = false;
268 this.rendered = false;
270 this.render();
269 this.render();
271 }
270 }
272 }
271 }
273 };
272 };
274
273
275 /** Generate JSON from cell
274 /** Generate JSON from cell
276 * @return {object} cell data serialised to json
275 * @return {object} cell data serialised to json
277 */
276 */
278 TextCell.prototype.toJSON = function () {
277 TextCell.prototype.toJSON = function () {
279 var data = IPython.Cell.prototype.toJSON.apply(this);
278 var data = IPython.Cell.prototype.toJSON.apply(this);
280 data.cell_type = this.cell_type;
279 data.cell_type = this.cell_type;
281 data.source = this.get_text();
280 data.source = this.get_text();
282 return data;
281 return data;
283 };
282 };
284
283
285
284
286 /**
285 /**
287 * @class MarkdownCell
286 * @class MarkdownCell
288 * @constructor MarkdownCell
287 * @constructor MarkdownCell
289 * @extends IPython.HTMLCell
288 * @extends IPython.HTMLCell
290 */
289 */
291 var MarkdownCell = function (options) {
290 var MarkdownCell = function (options) {
292 var options = options || {};
291 var options = options || {};
293
292
294 options = this.mergeopt(MarkdownCell,options);
293 options = this.mergeopt(MarkdownCell,options);
295 TextCell.apply(this, [options]);
294 TextCell.apply(this, [options]);
296
295
297 this.cell_type = 'markdown';
296 this.cell_type = 'markdown';
298 };
297 };
299
298
300 MarkdownCell.options_default = {
299 MarkdownCell.options_default = {
301 cm_config: {
300 cm_config: {
302 mode: 'gfm'
301 mode: 'gfm'
303 },
302 },
304 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
303 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
305 }
304 }
306
305
307
306
308
307
309
308
310 MarkdownCell.prototype = new TextCell();
309 MarkdownCell.prototype = new TextCell();
311
310
312 /**
311 /**
313 * @method render
312 * @method render
314 */
313 */
315 MarkdownCell.prototype.render = function () {
314 MarkdownCell.prototype.render = function () {
316 if (this.rendered === false) {
315 if (this.rendered === false) {
317 var text = this.get_text();
316 var text = this.get_text();
318 if (text === "") { text = this.placeholder; }
317 if (text === "") { text = this.placeholder; }
319 var text_math = IPython.mathjaxutils.remove_math(text);
318 var text_math = IPython.mathjaxutils.remove_math(text);
320 var text = text_math[0];
319 var text = text_math[0];
321 var math = text_math[1];
320 var math = text_math[1];
322 var html = marked.parser(marked.lexer(text));
321 var html = marked.parser(marked.lexer(text));
323 html = $(IPython.mathjaxutils.replace_math(html, math));
322 html = $(IPython.mathjaxutils.replace_math(html, math));
324 // links in markdown cells should open in new tabs
323 // links in markdown cells should open in new tabs
325 html.find("a[href]").attr("target", "_blank");
324 html.find("a[href]").attr("target", "_blank");
326 try {
325 try {
327 this.set_rendered(html);
326 this.set_rendered(html);
328 } catch (e) {
327 } catch (e) {
329 console.log("Error running Javascript in Markdown:");
328 console.log("Error running Javascript in Markdown:");
330 console.log(e);
329 console.log(e);
331 this.set_rendered($("<div/>").addClass("js-error").html(
330 this.set_rendered($("<div/>").addClass("js-error").html(
332 "Error rendering Markdown!<br/>" + e.toString())
331 "Error rendering Markdown!<br/>" + e.toString())
333 );
332 );
334 }
333 }
335 this.element.find('div.text_cell_input').hide();
334 this.element.find('div.text_cell_input').hide();
336 this.element.find("div.text_cell_render").show();
335 this.element.find("div.text_cell_render").show();
337 this.typeset()
336 this.typeset()
338 this.rendered = true;
337 this.rendered = true;
339 }
338 }
340 };
339 };
341
340
342
341
343 // RawCell
342 // RawCell
344
343
345 /**
344 /**
346 * @class RawCell
345 * @class RawCell
347 * @constructor RawCell
346 * @constructor RawCell
348 * @extends IPython.TextCell
347 * @extends IPython.TextCell
349 */
348 */
350 var RawCell = function (options) {
349 var RawCell = function (options) {
351
350
352 options = this.mergeopt(RawCell,options)
351 options = this.mergeopt(RawCell,options)
353 TextCell.apply(this, [options]);
352 TextCell.apply(this, [options]);
354
353
355 this.cell_type = 'raw';
354 this.cell_type = 'raw';
356
355
357 var that = this
356 var that = this
358 this.element.focusout(
357 this.element.focusout(
359 function() { that.auto_highlight(); }
358 function() { that.auto_highlight(); }
360 );
359 );
361 };
360 };
362
361
363 RawCell.options_default = {
362 RawCell.options_default = {
364 placeholder : "Type plain text and LaTeX: $\\alpha^2$"
363 placeholder : "Type plain text and LaTeX: $\\alpha^2$"
365 };
364 };
366
365
367
366
368
367
369 RawCell.prototype = new TextCell();
368 RawCell.prototype = new TextCell();
370
369
371 /**
370 /**
372 * Trigger autodetection of highlight scheme for current cell
371 * Trigger autodetection of highlight scheme for current cell
373 * @method auto_highlight
372 * @method auto_highlight
374 */
373 */
375 RawCell.prototype.auto_highlight = function () {
374 RawCell.prototype.auto_highlight = function () {
376 this._auto_highlight(IPython.config.raw_cell_highlight);
375 this._auto_highlight(IPython.config.raw_cell_highlight);
377 };
376 };
378
377
379 /** @method render **/
378 /** @method render **/
380 RawCell.prototype.render = function () {
379 RawCell.prototype.render = function () {
381 this.rendered = true;
380 this.rendered = true;
382 this.edit();
381 this.edit();
383 };
382 };
384
383
385
384
386 /** @method handle_codemirror_keyevent **/
385 /** @method handle_codemirror_keyevent **/
387 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
386 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
388
387
389 var that = this;
388 var that = this;
390 if (event.which === key.UPARROW && event.type === 'keydown') {
389 if (event.which === key.UPARROW && event.type === 'keydown') {
391 // If we are not at the top, let CM handle the up arrow and
390 // If we are not at the top, let CM handle the up arrow and
392 // prevent the global keydown handler from handling it.
391 // prevent the global keydown handler from handling it.
393 if (!that.at_top()) {
392 if (!that.at_top()) {
394 event.stop();
393 event.stop();
395 return false;
394 return false;
396 } else {
395 } else {
397 return true;
396 return true;
398 };
397 };
399 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
398 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
400 // If we are not at the bottom, let CM handle the down arrow and
399 // If we are not at the bottom, let CM handle the down arrow and
401 // prevent the global keydown handler from handling it.
400 // prevent the global keydown handler from handling it.
402 if (!that.at_bottom()) {
401 if (!that.at_bottom()) {
403 event.stop();
402 event.stop();
404 return false;
403 return false;
405 } else {
404 } else {
406 return true;
405 return true;
407 };
406 };
408 };
407 };
409 return false;
408 return false;
410 };
409 };
411
410
412 /** @method select **/
411 /** @method select **/
413 RawCell.prototype.select = function () {
412 RawCell.prototype.select = function () {
414 IPython.Cell.prototype.select.apply(this);
413 IPython.Cell.prototype.select.apply(this);
415 this.code_mirror.refresh();
414 this.code_mirror.refresh();
416 this.code_mirror.focus();
415 this.code_mirror.focus();
417 };
416 };
418
417
419 /** @method at_top **/
418 /** @method at_top **/
420 RawCell.prototype.at_top = function () {
419 RawCell.prototype.at_top = function () {
421 var cursor = this.code_mirror.getCursor();
420 var cursor = this.code_mirror.getCursor();
422 if (cursor.line === 0 && cursor.ch === 0) {
421 if (cursor.line === 0 && cursor.ch === 0) {
423 return true;
422 return true;
424 } else {
423 } else {
425 return false;
424 return false;
426 }
425 }
427 };
426 };
428
427
429
428
430 /** @method at_bottom **/
429 /** @method at_bottom **/
431 RawCell.prototype.at_bottom = function () {
430 RawCell.prototype.at_bottom = function () {
432 var cursor = this.code_mirror.getCursor();
431 var cursor = this.code_mirror.getCursor();
433 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
432 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
434 return true;
433 return true;
435 } else {
434 } else {
436 return false;
435 return false;
437 }
436 }
438 };
437 };
439
438
440
439
441 /**
440 /**
442 * @class HeadingCell
441 * @class HeadingCell
443 * @extends IPython.TextCell
442 * @extends IPython.TextCell
444 */
443 */
445
444
446 /**
445 /**
447 * @constructor HeadingCell
446 * @constructor HeadingCell
448 * @extends IPython.TextCell
447 * @extends IPython.TextCell
449 */
448 */
450 var HeadingCell = function (options) {
449 var HeadingCell = function (options) {
451
450
452 options = this.mergeopt(HeadingCell,options)
451 options = this.mergeopt(HeadingCell,options)
453 TextCell.apply(this, [options]);
452 TextCell.apply(this, [options]);
454
453
455 /**
454 /**
456 * heading level of the cell, use getter and setter to access
455 * heading level of the cell, use getter and setter to access
457 * @property level
456 * @property level
458 */
457 */
459 this.level = 1;
458 this.level = 1;
460 this.cell_type = 'heading';
459 this.cell_type = 'heading';
461 };
460 };
462
461
463 HeadingCell.options_default = {
462 HeadingCell.options_default = {
464 placeholder: "Type Heading Here"
463 placeholder: "Type Heading Here"
465 };
464 };
466
465
467 HeadingCell.prototype = new TextCell();
466 HeadingCell.prototype = new TextCell();
468
467
469 /** @method fromJSON */
468 /** @method fromJSON */
470 HeadingCell.prototype.fromJSON = function (data) {
469 HeadingCell.prototype.fromJSON = function (data) {
471 if (data.level != undefined){
470 if (data.level != undefined){
472 this.level = data.level;
471 this.level = data.level;
473 }
472 }
474 TextCell.prototype.fromJSON.apply(this, arguments);
473 TextCell.prototype.fromJSON.apply(this, arguments);
475 };
474 };
476
475
477
476
478 /** @method toJSON */
477 /** @method toJSON */
479 HeadingCell.prototype.toJSON = function () {
478 HeadingCell.prototype.toJSON = function () {
480 var data = TextCell.prototype.toJSON.apply(this);
479 var data = TextCell.prototype.toJSON.apply(this);
481 data.level = this.get_level();
480 data.level = this.get_level();
482 return data;
481 return data;
483 };
482 };
484
483
485
484
486 /**
485 /**
487 * Change heading level of cell, and re-render
486 * Change heading level of cell, and re-render
488 * @method set_level
487 * @method set_level
489 */
488 */
490 HeadingCell.prototype.set_level = function (level) {
489 HeadingCell.prototype.set_level = function (level) {
491 this.level = level;
490 this.level = level;
492 if (this.rendered) {
491 if (this.rendered) {
493 this.rendered = false;
492 this.rendered = false;
494 this.render();
493 this.render();
495 };
494 };
496 };
495 };
497
496
498 /** The depth of header cell, based on html (h1 to h6)
497 /** The depth of header cell, based on html (h1 to h6)
499 * @method get_level
498 * @method get_level
500 * @return {integer} level - for 1 to 6
499 * @return {integer} level - for 1 to 6
501 */
500 */
502 HeadingCell.prototype.get_level = function () {
501 HeadingCell.prototype.get_level = function () {
503 return this.level;
502 return this.level;
504 };
503 };
505
504
506
505
507 HeadingCell.prototype.set_rendered = function (html) {
506 HeadingCell.prototype.set_rendered = function (html) {
508 this.element.find("div.text_cell_render").html(html);
507 this.element.find("div.text_cell_render").html(html);
509 };
508 };
510
509
511
510
512 HeadingCell.prototype.get_rendered = function () {
511 HeadingCell.prototype.get_rendered = function () {
513 var r = this.element.find("div.text_cell_render");
512 var r = this.element.find("div.text_cell_render");
514 return r.children().first().html();
513 return r.children().first().html();
515 };
514 };
516
515
517
516
518 HeadingCell.prototype.render = function () {
517 HeadingCell.prototype.render = function () {
519 if (this.rendered === false) {
518 if (this.rendered === false) {
520 var text = this.get_text();
519 var text = this.get_text();
521 // Markdown headings must be a single line
520 // Markdown headings must be a single line
522 text = text.replace(/\n/g, ' ');
521 text = text.replace(/\n/g, ' ');
523 if (text === "") { text = this.placeholder; }
522 if (text === "") { text = this.placeholder; }
524 text = Array(this.level + 1).join("#") + " " + text;
523 text = Array(this.level + 1).join("#") + " " + text;
525 var text_and_math = IPython.mathjaxutils.remove_math(text);
524 var text_and_math = IPython.mathjaxutils.remove_math(text);
526 var text = text_and_math[0];
525 var text = text_and_math[0];
527 var math = text_and_math[1];
526 var math = text_and_math[1];
528 var html = marked.parser(marked.lexer(text));
527 var html = marked.parser(marked.lexer(text));
529 var h = $(IPython.mathjaxutils.replace_math(html, math));
528 var h = $(IPython.mathjaxutils.replace_math(html, math));
530 // add id and linkback anchor
529 // add id and linkback anchor
531 var hash = h.text().replace(/ /g, '-');
530 var hash = h.text().replace(/ /g, '-');
532 h.attr('id', hash);
531 h.attr('id', hash);
533 h.append(
532 h.append(
534 $('<a/>')
533 $('<a/>')
535 .addClass('anchor-link')
534 .addClass('anchor-link')
536 .attr('href', '#' + hash)
535 .attr('href', '#' + hash)
537 .text('ΒΆ')
536 .text('ΒΆ')
538 );
537 );
539
538
540 this.set_rendered(h);
539 this.set_rendered(h);
541 this.typeset();
540 this.typeset();
542 this.element.find('div.text_cell_input').hide();
541 this.element.find('div.text_cell_input').hide();
543 this.element.find("div.text_cell_render").show();
542 this.element.find("div.text_cell_render").show();
544 this.rendered = true;
543 this.rendered = true;
545 };
544 };
546 };
545 };
547
546
548 IPython.TextCell = TextCell;
547 IPython.TextCell = TextCell;
549 IPython.MarkdownCell = MarkdownCell;
548 IPython.MarkdownCell = MarkdownCell;
550 IPython.RawCell = RawCell;
549 IPython.RawCell = RawCell;
551 IPython.HeadingCell = HeadingCell;
550 IPython.HeadingCell = HeadingCell;
552
551
553
552
554 return IPython;
553 return IPython;
555
554
556 }(IPython));
555 }(IPython));
557
556
@@ -1,85 +1,84 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // On document ready
9 // On document ready
10 //============================================================================
10 //============================================================================
11
11
12
12
13 $(document).ready(function () {
13 $(document).ready(function () {
14
14
15 IPython.page = new IPython.Page();
15 IPython.page = new IPython.Page();
16 $('#new_notebook').click(function (e) {
16 $('#new_notebook').click(function (e) {
17 window.open($('body').data('baseProjectUrl')+'new');
17 window.open($('body').data('baseProjectUrl')+'new');
18 });
18 });
19
19
20 IPython.read_only = $('body').data('readOnly') === 'True';
21 IPython.notebook_list = new IPython.NotebookList('#notebook_list');
20 IPython.notebook_list = new IPython.NotebookList('#notebook_list');
22 IPython.cluster_list = new IPython.ClusterList('#cluster_list');
21 IPython.cluster_list = new IPython.ClusterList('#cluster_list');
23 IPython.login_widget = new IPython.LoginWidget('#login_widget');
22 IPython.login_widget = new IPython.LoginWidget('#login_widget');
24
23
25 var interval_id=0;
24 var interval_id=0;
26 // auto refresh every xx secondes, no need to be fast,
25 // auto refresh every xx secondes, no need to be fast,
27 // update is done at least when page get focus
26 // update is done at least when page get focus
28 var time_refresh = 60; // in sec
27 var time_refresh = 60; // in sec
29
28
30 var enable_autorefresh = function(){
29 var enable_autorefresh = function(){
31 //refresh immediately , then start interval
30 //refresh immediately , then start interval
32 if($('.upload_button').length == 0)
31 if($('.upload_button').length == 0)
33 {
32 {
34 IPython.notebook_list.load_list();
33 IPython.notebook_list.load_list();
35 IPython.cluster_list.load_list();
34 IPython.cluster_list.load_list();
36 }
35 }
37 if (!interval_id){
36 if (!interval_id){
38 interval_id = setInterval(function(){
37 interval_id = setInterval(function(){
39 if($('.upload_button').length == 0)
38 if($('.upload_button').length == 0)
40 {
39 {
41 IPython.notebook_list.load_list();
40 IPython.notebook_list.load_list();
42 IPython.cluster_list.load_list();
41 IPython.cluster_list.load_list();
43 }
42 }
44 }, time_refresh*1000);
43 }, time_refresh*1000);
45 }
44 }
46 }
45 }
47
46
48 var disable_autorefresh = function(){
47 var disable_autorefresh = function(){
49 clearInterval(interval_id);
48 clearInterval(interval_id);
50 interval_id = 0;
49 interval_id = 0;
51 }
50 }
52
51
53 // stop autorefresh when page lose focus
52 // stop autorefresh when page lose focus
54 $(window).blur(function() {
53 $(window).blur(function() {
55 disable_autorefresh();
54 disable_autorefresh();
56 })
55 })
57
56
58 //re-enable when page get focus back
57 //re-enable when page get focus back
59 $(window).focus(function() {
58 $(window).focus(function() {
60 enable_autorefresh();
59 enable_autorefresh();
61 });
60 });
62
61
63 // finally start it, it will refresh immediately
62 // finally start it, it will refresh immediately
64 enable_autorefresh();
63 enable_autorefresh();
65
64
66 IPython.page.show();
65 IPython.page.show();
67
66
68 // bound the upload method to the on change of the file select list
67 // bound the upload method to the on change of the file select list
69 $("#alternate_upload").change(function (event){
68 $("#alternate_upload").change(function (event){
70 IPython.notebook_list.handelFilesUpload(event,'form');
69 IPython.notebook_list.handelFilesUpload(event,'form');
71 });
70 });
72
71
73 // set hash on tab click
72 // set hash on tab click
74 $("#tabs").find("a").click(function() {
73 $("#tabs").find("a").click(function() {
75 window.location.hash = $(this).attr("href");
74 window.location.hash = $(this).attr("href");
76 })
75 })
77
76
78 // load tab if url hash
77 // load tab if url hash
79 if (window.location.hash) {
78 if (window.location.hash) {
80 $("#tabs").find("a[href=" + window.location.hash + "]").click();
79 $("#tabs").find("a[href=" + window.location.hash + "]").click();
81 }
80 }
82
81
83
82
84 });
83 });
85
84
@@ -1,305 +1,300 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // NotebookList
9 // NotebookList
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var NotebookList = function (selector) {
14 var NotebookList = function (selector) {
15 this.selector = selector;
15 this.selector = selector;
16 if (this.selector !== undefined) {
16 if (this.selector !== undefined) {
17 this.element = $(selector);
17 this.element = $(selector);
18 this.style();
18 this.style();
19 this.bind_events();
19 this.bind_events();
20 }
20 }
21 };
21 };
22
22
23 NotebookList.prototype.baseProjectUrl = function () {
23 NotebookList.prototype.baseProjectUrl = function () {
24 return $('body').data('baseProjectUrl')
24 return $('body').data('baseProjectUrl')
25 };
25 };
26
26
27 NotebookList.prototype.style = function () {
27 NotebookList.prototype.style = function () {
28 $('#notebook_toolbar').addClass('list_toolbar');
28 $('#notebook_toolbar').addClass('list_toolbar');
29 $('#drag_info').addClass('toolbar_info');
29 $('#drag_info').addClass('toolbar_info');
30 $('#notebook_buttons').addClass('toolbar_buttons');
30 $('#notebook_buttons').addClass('toolbar_buttons');
31 $('#notebook_list_header').addClass('list_header');
31 $('#notebook_list_header').addClass('list_header');
32 this.element.addClass("list_container");
32 this.element.addClass("list_container");
33 };
33 };
34
34
35
35
36 NotebookList.prototype.bind_events = function () {
36 NotebookList.prototype.bind_events = function () {
37 if (IPython.read_only){
38 return;
39 }
40 var that = this;
37 var that = this;
41 $('#refresh_notebook_list').click(function () {
38 $('#refresh_notebook_list').click(function () {
42 that.load_list();
39 that.load_list();
43 });
40 });
44 this.element.bind('dragover', function () {
41 this.element.bind('dragover', function () {
45 return false;
42 return false;
46 });
43 });
47 this.element.bind('drop', function(event){
44 this.element.bind('drop', function(event){
48 that.handelFilesUpload(event,'drop');
45 that.handelFilesUpload(event,'drop');
49 return false;
46 return false;
50 });
47 });
51 };
48 };
52
49
53 NotebookList.prototype.handelFilesUpload = function(event, dropOrForm) {
50 NotebookList.prototype.handelFilesUpload = function(event, dropOrForm) {
54 var that = this;
51 var that = this;
55 var files;
52 var files;
56 if(dropOrForm =='drop'){
53 if(dropOrForm =='drop'){
57 files = event.originalEvent.dataTransfer.files;
54 files = event.originalEvent.dataTransfer.files;
58 } else
55 } else
59 {
56 {
60 files = event.originalEvent.target.files
57 files = event.originalEvent.target.files
61 }
58 }
62 for (var i = 0, f; f = files[i]; i++) {
59 for (var i = 0, f; f = files[i]; i++) {
63 var reader = new FileReader();
60 var reader = new FileReader();
64 reader.readAsText(f);
61 reader.readAsText(f);
65 var fname = f.name.split('.');
62 var fname = f.name.split('.');
66 var nbname = fname.slice(0,-1).join('.');
63 var nbname = fname.slice(0,-1).join('.');
67 var nbformat = fname.slice(-1)[0];
64 var nbformat = fname.slice(-1)[0];
68 if (nbformat === 'ipynb') {nbformat = 'json';};
65 if (nbformat === 'ipynb') {nbformat = 'json';};
69 if (nbformat === 'py' || nbformat === 'json') {
66 if (nbformat === 'py' || nbformat === 'json') {
70 var item = that.new_notebook_item(0);
67 var item = that.new_notebook_item(0);
71 that.add_name_input(nbname, item);
68 that.add_name_input(nbname, item);
72 item.data('nbformat', nbformat);
69 item.data('nbformat', nbformat);
73 // Store the notebook item in the reader so we can use it later
70 // Store the notebook item in the reader so we can use it later
74 // to know which item it belongs to.
71 // to know which item it belongs to.
75 $(reader).data('item', item);
72 $(reader).data('item', item);
76 reader.onload = function (event) {
73 reader.onload = function (event) {
77 var nbitem = $(event.target).data('item');
74 var nbitem = $(event.target).data('item');
78 that.add_notebook_data(event.target.result, nbitem);
75 that.add_notebook_data(event.target.result, nbitem);
79 that.add_upload_button(nbitem);
76 that.add_upload_button(nbitem);
80 };
77 };
81 };
78 };
82 }
79 }
83 return false;
80 return false;
84 };
81 };
85
82
86 NotebookList.prototype.clear_list = function () {
83 NotebookList.prototype.clear_list = function () {
87 this.element.children('.list_item').remove();
84 this.element.children('.list_item').remove();
88 };
85 };
89
86
90
87
91 NotebookList.prototype.load_list = function () {
88 NotebookList.prototype.load_list = function () {
92 var that = this;
89 var that = this;
93 var settings = {
90 var settings = {
94 processData : false,
91 processData : false,
95 cache : false,
92 cache : false,
96 type : "GET",
93 type : "GET",
97 dataType : "json",
94 dataType : "json",
98 success : $.proxy(this.list_loaded, this),
95 success : $.proxy(this.list_loaded, this),
99 error : $.proxy( function(){
96 error : $.proxy( function(){
100 that.list_loaded([], null, null, {msg:"Error connecting to server."});
97 that.list_loaded([], null, null, {msg:"Error connecting to server."});
101 },this)
98 },this)
102 };
99 };
103
100
104 var url = this.baseProjectUrl() + 'notebooks';
101 var url = this.baseProjectUrl() + 'notebooks';
105 $.ajax(url, settings);
102 $.ajax(url, settings);
106 };
103 };
107
104
108
105
109 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
106 NotebookList.prototype.list_loaded = function (data, status, xhr, param) {
110 var message = 'Notebook list empty.';
107 var message = 'Notebook list empty.';
111 if (param !== undefined && param.msg) {
108 if (param !== undefined && param.msg) {
112 var message = param.msg;
109 var message = param.msg;
113 }
110 }
114 var len = data.length;
111 var len = data.length;
115 this.clear_list();
112 this.clear_list();
116
113
117 if(len == 0)
114 if(len == 0)
118 {
115 {
119 $(this.new_notebook_item(0))
116 $(this.new_notebook_item(0))
120 .append(
117 .append(
121 $('<div style="margin:auto;text-align:center;color:grey"/>')
118 $('<div style="margin:auto;text-align:center;color:grey"/>')
122 .text(message)
119 .text(message)
123 )
120 )
124 }
121 }
125
122
126 for (var i=0; i<len; i++) {
123 for (var i=0; i<len; i++) {
127 var notebook_id = data[i].notebook_id;
124 var notebook_id = data[i].notebook_id;
128 var nbname = data[i].name;
125 var nbname = data[i].name;
129 var kernel = data[i].kernel_id;
126 var kernel = data[i].kernel_id;
130 var item = this.new_notebook_item(i);
127 var item = this.new_notebook_item(i);
131 this.add_link(notebook_id, nbname, item);
128 this.add_link(notebook_id, nbname, item);
132 if (!IPython.read_only){
129 // hide delete buttons when readonly
133 // hide delete buttons when readonly
130 if(kernel == null){
134 if(kernel == null){
131 this.add_delete_button(item);
135 this.add_delete_button(item);
132 } else {
136 } else {
133 this.add_shutdown_button(item,kernel);
137 this.add_shutdown_button(item,kernel);
138 }
139 }
134 }
140 };
135 };
141 };
136 };
142
137
143
138
144 NotebookList.prototype.new_notebook_item = function (index) {
139 NotebookList.prototype.new_notebook_item = function (index) {
145 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
140 var item = $('<div/>').addClass("list_item").addClass("row-fluid");
146 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
141 // item.addClass('list_item ui-widget ui-widget-content ui-helper-clearfix');
147 // item.css('border-top-style','none');
142 // item.css('border-top-style','none');
148 item.append($("<div/>").addClass("span12").append(
143 item.append($("<div/>").addClass("span12").append(
149 $("<a/>").addClass("item_link").append(
144 $("<a/>").addClass("item_link").append(
150 $("<span/>").addClass("item_name")
145 $("<span/>").addClass("item_name")
151 )
146 )
152 ).append(
147 ).append(
153 $('<div/>').addClass("item_buttons btn-group pull-right")
148 $('<div/>').addClass("item_buttons btn-group pull-right")
154 ));
149 ));
155
150
156 if (index === -1) {
151 if (index === -1) {
157 this.element.append(item);
152 this.element.append(item);
158 } else {
153 } else {
159 this.element.children().eq(index).after(item);
154 this.element.children().eq(index).after(item);
160 }
155 }
161 return item;
156 return item;
162 };
157 };
163
158
164
159
165 NotebookList.prototype.add_link = function (notebook_id, nbname, item) {
160 NotebookList.prototype.add_link = function (notebook_id, nbname, item) {
166 item.data('nbname', nbname);
161 item.data('nbname', nbname);
167 item.data('notebook_id', notebook_id);
162 item.data('notebook_id', notebook_id);
168 item.find(".item_name").text(nbname);
163 item.find(".item_name").text(nbname);
169 item.find("a.item_link")
164 item.find("a.item_link")
170 .attr('href', this.baseProjectUrl()+notebook_id)
165 .attr('href', this.baseProjectUrl()+notebook_id)
171 .attr('target','_blank');
166 .attr('target','_blank');
172 };
167 };
173
168
174
169
175 NotebookList.prototype.add_name_input = function (nbname, item) {
170 NotebookList.prototype.add_name_input = function (nbname, item) {
176 item.data('nbname', nbname);
171 item.data('nbname', nbname);
177 item.find(".item_name").empty().append(
172 item.find(".item_name").empty().append(
178 $('<input/>')
173 $('<input/>')
179 .addClass("nbname_input")
174 .addClass("nbname_input")
180 .attr('value', nbname)
175 .attr('value', nbname)
181 .attr('size', '30')
176 .attr('size', '30')
182 .attr('type', 'text')
177 .attr('type', 'text')
183 );
178 );
184 };
179 };
185
180
186
181
187 NotebookList.prototype.add_notebook_data = function (data, item) {
182 NotebookList.prototype.add_notebook_data = function (data, item) {
188 item.data('nbdata',data);
183 item.data('nbdata',data);
189 };
184 };
190
185
191
186
192 NotebookList.prototype.add_shutdown_button = function (item, kernel) {
187 NotebookList.prototype.add_shutdown_button = function (item, kernel) {
193 var that = this;
188 var that = this;
194 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini").
189 var shutdown_button = $("<button/>").text("Shutdown").addClass("btn btn-mini").
195 click(function (e) {
190 click(function (e) {
196 var settings = {
191 var settings = {
197 processData : false,
192 processData : false,
198 cache : false,
193 cache : false,
199 type : "DELETE",
194 type : "DELETE",
200 dataType : "json",
195 dataType : "json",
201 success : function (data, status, xhr) {
196 success : function (data, status, xhr) {
202 that.load_list();
197 that.load_list();
203 }
198 }
204 };
199 };
205 var url = that.baseProjectUrl() + 'kernels/'+kernel;
200 var url = that.baseProjectUrl() + 'kernels/'+kernel;
206 $.ajax(url, settings);
201 $.ajax(url, settings);
207 return false;
202 return false;
208 });
203 });
209 // var new_buttons = item.find('a'); // shutdown_button;
204 // var new_buttons = item.find('a'); // shutdown_button;
210 item.find(".item_buttons").html("").append(shutdown_button);
205 item.find(".item_buttons").html("").append(shutdown_button);
211 };
206 };
212
207
213 NotebookList.prototype.add_delete_button = function (item) {
208 NotebookList.prototype.add_delete_button = function (item) {
214 var new_buttons = $('<span/>').addClass("btn-group pull-right");
209 var new_buttons = $('<span/>').addClass("btn-group pull-right");
215 var notebooklist = this;
210 var notebooklist = this;
216 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
211 var delete_button = $("<button/>").text("Delete").addClass("btn btn-mini").
217 click(function (e) {
212 click(function (e) {
218 // $(this) is the button that was clicked.
213 // $(this) is the button that was clicked.
219 var that = $(this);
214 var that = $(this);
220 // We use the nbname and notebook_id from the parent notebook_item element's
215 // We use the nbname and notebook_id from the parent notebook_item element's
221 // data because the outer scopes values change as we iterate through the loop.
216 // data because the outer scopes values change as we iterate through the loop.
222 var parent_item = that.parents('div.list_item');
217 var parent_item = that.parents('div.list_item');
223 var nbname = parent_item.data('nbname');
218 var nbname = parent_item.data('nbname');
224 var notebook_id = parent_item.data('notebook_id');
219 var notebook_id = parent_item.data('notebook_id');
225 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
220 var message = 'Are you sure you want to permanently delete the notebook: ' + nbname + '?';
226 IPython.dialog.modal({
221 IPython.dialog.modal({
227 title : "Delete notebook",
222 title : "Delete notebook",
228 body : message,
223 body : message,
229 buttons : {
224 buttons : {
230 Delete : {
225 Delete : {
231 class: "btn-danger",
226 class: "btn-danger",
232 click: function() {
227 click: function() {
233 var settings = {
228 var settings = {
234 processData : false,
229 processData : false,
235 cache : false,
230 cache : false,
236 type : "DELETE",
231 type : "DELETE",
237 dataType : "json",
232 dataType : "json",
238 success : function (data, status, xhr) {
233 success : function (data, status, xhr) {
239 parent_item.remove();
234 parent_item.remove();
240 }
235 }
241 };
236 };
242 var url = notebooklist.baseProjectUrl() + 'notebooks/' + notebook_id;
237 var url = notebooklist.baseProjectUrl() + 'notebooks/' + notebook_id;
243 $.ajax(url, settings);
238 $.ajax(url, settings);
244 }
239 }
245 },
240 },
246 Cancel : {}
241 Cancel : {}
247 }
242 }
248 });
243 });
249 return false;
244 return false;
250 });
245 });
251 item.find(".item_buttons").html("").append(delete_button);
246 item.find(".item_buttons").html("").append(delete_button);
252 };
247 };
253
248
254
249
255 NotebookList.prototype.add_upload_button = function (item) {
250 NotebookList.prototype.add_upload_button = function (item) {
256 var that = this;
251 var that = this;
257 var upload_button = $('<button/>').text("Upload")
252 var upload_button = $('<button/>').text("Upload")
258 .addClass('btn btn-primary btn-mini upload_button')
253 .addClass('btn btn-primary btn-mini upload_button')
259 .click(function (e) {
254 .click(function (e) {
260 var nbname = item.find('.item_name > input').attr('value');
255 var nbname = item.find('.item_name > input').attr('value');
261 var nbformat = item.data('nbformat');
256 var nbformat = item.data('nbformat');
262 var nbdata = item.data('nbdata');
257 var nbdata = item.data('nbdata');
263 var content_type = 'text/plain';
258 var content_type = 'text/plain';
264 if (nbformat === 'json') {
259 if (nbformat === 'json') {
265 content_type = 'application/json';
260 content_type = 'application/json';
266 } else if (nbformat === 'py') {
261 } else if (nbformat === 'py') {
267 content_type = 'application/x-python';
262 content_type = 'application/x-python';
268 };
263 };
269 var settings = {
264 var settings = {
270 processData : false,
265 processData : false,
271 cache : false,
266 cache : false,
272 type : 'POST',
267 type : 'POST',
273 dataType : 'json',
268 dataType : 'json',
274 data : nbdata,
269 data : nbdata,
275 headers : {'Content-Type': content_type},
270 headers : {'Content-Type': content_type},
276 success : function (data, status, xhr) {
271 success : function (data, status, xhr) {
277 that.add_link(data, nbname, item);
272 that.add_link(data, nbname, item);
278 that.add_delete_button(item);
273 that.add_delete_button(item);
279 }
274 }
280 };
275 };
281
276
282 var qs = $.param({name:nbname, format:nbformat});
277 var qs = $.param({name:nbname, format:nbformat});
283 var url = that.baseProjectUrl() + 'notebooks?' + qs;
278 var url = that.baseProjectUrl() + 'notebooks?' + qs;
284 $.ajax(url, settings);
279 $.ajax(url, settings);
285 return false;
280 return false;
286 });
281 });
287 var cancel_button = $('<button/>').text("Cancel")
282 var cancel_button = $('<button/>').text("Cancel")
288 .addClass("btn btn-mini")
283 .addClass("btn btn-mini")
289 .click(function (e) {
284 .click(function (e) {
290 console.log('cancel click');
285 console.log('cancel click');
291 item.remove();
286 item.remove();
292 return false;
287 return false;
293 });
288 });
294 item.find(".item_buttons").empty()
289 item.find(".item_buttons").empty()
295 .append(upload_button)
290 .append(upload_button)
296 .append(cancel_button);
291 .append(cancel_button);
297 };
292 };
298
293
299
294
300 IPython.NotebookList = NotebookList;
295 IPython.NotebookList = NotebookList;
301
296
302 return IPython;
297 return IPython;
303
298
304 }(IPython));
299 }(IPython));
305
300
@@ -1,38 +1,38 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4 {{super()}}
4 {{super()}}
5 <link rel="stylesheet" href="{{ static_url("auth/css/override.css") }}" type="text/css" />
5 <link rel="stylesheet" href="{{ static_url("auth/css/override.css") }}" type="text/css" />
6 {% endblock %}
6 {% endblock %}
7
7
8 {% block login_widget %}
8 {% block login_widget %}
9 {% endblock %}
9 {% endblock %}
10
10
11 {% block site %}
11 {% block site %}
12
12
13 <div id="ipython-main-app" class="container">
13 <div id="ipython-main-app" class="container">
14
14
15 {% if message %}
15 {% if message %}
16 {% for key in message %}
16 {% for key in message %}
17 <div class="message {{key}}">
17 <div class="message {{key}}">
18 {{message[key]}}
18 {{message[key]}}
19 </div>
19 </div>
20 {% endfor %}
20 {% endfor %}
21 {% endif %}
21 {% endif %}
22
22
23 {% if read_only or not login_available %}
23 {% if not login_available %}
24 Proceed to the <a href="{{base_project_url}}">dashboard</a>.
24 Proceed to the <a href="{{base_project_url}}">dashboard</a>.
25 {% else %}
25 {% else %}
26 Proceed to the <a href="{{base_project_url}}login">login page</a>.
26 Proceed to the <a href="{{base_project_url}}login">login page</a>.
27 {% endif %}
27 {% endif %}
28
28
29
29
30 <div/>
30 <div/>
31
31
32 {% endblock %}
32 {% endblock %}
33
33
34 {% block script %}
34 {% block script %}
35
35
36 <script src="{{static_url("auth/js/logoutmain.js") }}" type="text/javascript" charset="utf-8"></script>
36 <script src="{{static_url("auth/js/logoutmain.js") }}" type="text/javascript" charset="utf-8"></script>
37
37
38 {% endblock %}
38 {% endblock %}
@@ -1,258 +1,257 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4
4
5 {% if mathjax_url %}
5 {% if mathjax_url %}
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 {% endif %}
7 {% endif %}
8 <script type="text/javascript">
8 <script type="text/javascript">
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 // where it will be undefined, and should prompt a dialog later.
10 // where it will be undefined, and should prompt a dialog later.
11 window.mathjax_url = "{{mathjax_url}}";
11 window.mathjax_url = "{{mathjax_url}}";
12 </script>
12 </script>
13
13
14 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
14 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
15
15
16 {{super()}}
16 {{super()}}
17
17
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
19
19
20 {% endblock %}
20 {% endblock %}
21
21
22 {% block params %}
22 {% block params %}
23
23
24 data-project={{project}}
24 data-project={{project}}
25 data-base-project-url={{base_project_url}}
25 data-base-project-url={{base_project_url}}
26 data-base-kernel-url={{base_kernel_url}}
26 data-base-kernel-url={{base_kernel_url}}
27 data-read-only={{read_only and not logged_in}}
28 data-notebook-id={{notebook_id}}
27 data-notebook-id={{notebook_id}}
29 class="notebook_app"
28 class="notebook_app"
30
29
31 {% endblock %}
30 {% endblock %}
32
31
33
32
34 {% block header %}
33 {% block header %}
35
34
36 <span id="save_widget" class="nav pull-left">
35 <span id="save_widget" class="nav pull-left">
37 <span id="notebook_name"></span>
36 <span id="notebook_name"></span>
38 <span id="checkpoint_status"></span>
37 <span id="checkpoint_status"></span>
39 <span id="autosave_status"></span>
38 <span id="autosave_status"></span>
40 </span>
39 </span>
41
40
42 {% endblock %}
41 {% endblock %}
43
42
44
43
45 {% block site %}
44 {% block site %}
46
45
47 <div id="menubar-container" class="container">
46 <div id="menubar-container" class="container">
48 <div id="menubar">
47 <div id="menubar">
49 <div class="navbar">
48 <div class="navbar">
50 <div class="navbar-inner">
49 <div class="navbar-inner">
51 <div class="container">
50 <div class="container">
52 <ul id="menus" class="nav">
51 <ul id="menus" class="nav">
53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
52 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
54 <ul class="dropdown-menu">
53 <ul class="dropdown-menu">
55 <li id="new_notebook"><a href="#">New</a></li>
54 <li id="new_notebook"><a href="#">New</a></li>
56 <li id="open_notebook"><a href="#">Open...</a></li>
55 <li id="open_notebook"><a href="#">Open...</a></li>
57 <!-- <hr/> -->
56 <!-- <hr/> -->
58 <li class="divider"></li>
57 <li class="divider"></li>
59 <li id="copy_notebook"><a href="#">Make a Copy...</a></li>
58 <li id="copy_notebook"><a href="#">Make a Copy...</a></li>
60 <li id="rename_notebook"><a href="#">Rename...</a></li>
59 <li id="rename_notebook"><a href="#">Rename...</a></li>
61 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
60 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
62 <!-- <hr/> -->
61 <!-- <hr/> -->
63 <li class="divider"></li>
62 <li class="divider"></li>
64 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
63 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
65 <ul class="dropdown-menu">
64 <ul class="dropdown-menu">
66 <li><a href="#"></a></li>
65 <li><a href="#"></a></li>
67 <li><a href="#"></a></li>
66 <li><a href="#"></a></li>
68 <li><a href="#"></a></li>
67 <li><a href="#"></a></li>
69 <li><a href="#"></a></li>
68 <li><a href="#"></a></li>
70 <li><a href="#"></a></li>
69 <li><a href="#"></a></li>
71 </ul>
70 </ul>
72 </li>
71 </li>
73 <li class="divider"></li>
72 <li class="divider"></li>
74 <li class="dropdown-submenu"><a href="#">Download as</a>
73 <li class="dropdown-submenu"><a href="#">Download as</a>
75 <ul class="dropdown-menu">
74 <ul class="dropdown-menu">
76 <li id="download_ipynb"><a href="#">IPython (.ipynb)</a></li>
75 <li id="download_ipynb"><a href="#">IPython (.ipynb)</a></li>
77 <li id="download_py"><a href="#">Python (.py)</a></li>
76 <li id="download_py"><a href="#">Python (.py)</a></li>
78 </ul>
77 </ul>
79 </li>
78 </li>
80 <li class="divider"></li>
79 <li class="divider"></li>
81
80
82 <li id="kill_and_exit"><a href="#" >Close and halt</a></li>
81 <li id="kill_and_exit"><a href="#" >Close and halt</a></li>
83 </ul>
82 </ul>
84 </li>
83 </li>
85 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
84 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
86 <ul class="dropdown-menu">
85 <ul class="dropdown-menu">
87 <li id="cut_cell"><a href="#">Cut Cell</a></li>
86 <li id="cut_cell"><a href="#">Cut Cell</a></li>
88 <li id="copy_cell"><a href="#">Copy Cell</a></li>
87 <li id="copy_cell"><a href="#">Copy Cell</a></li>
89 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
88 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
90 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
89 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
91 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
90 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
92 <li id="delete_cell"><a href="#">Delete Cell</a></li>
91 <li id="delete_cell"><a href="#">Delete Cell</a></li>
93 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
92 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
94 <li class="divider"></li>
93 <li class="divider"></li>
95 <li id="split_cell"><a href="#">Split Cell</a></li>
94 <li id="split_cell"><a href="#">Split Cell</a></li>
96 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
95 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
97 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
96 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
98 <li class="divider"></li>
97 <li class="divider"></li>
99 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
98 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
100 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
99 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
101 <li class="divider"></li>
100 <li class="divider"></li>
102 <li id="select_previous"><a href="#">Select Previous Cell</a></li>
101 <li id="select_previous"><a href="#">Select Previous Cell</a></li>
103 <li id="select_next"><a href="#">Select Next Cell</a></li>
102 <li id="select_next"><a href="#">Select Next Cell</a></li>
104 </ul>
103 </ul>
105 </li>
104 </li>
106 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
105 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
107 <ul class="dropdown-menu">
106 <ul class="dropdown-menu">
108 <li id="toggle_header"><a href="#">Toggle Header</a></li>
107 <li id="toggle_header"><a href="#">Toggle Header</a></li>
109 <li id="toggle_toolbar"><a href="#">Toggle Toolbar</a></li>
108 <li id="toggle_toolbar"><a href="#">Toggle Toolbar</a></li>
110 </ul>
109 </ul>
111 </li>
110 </li>
112 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
111 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
113 <ul class="dropdown-menu">
112 <ul class="dropdown-menu">
114 <li id="insert_cell_above"><a href="#">Insert Cell Above</a></li>
113 <li id="insert_cell_above"><a href="#">Insert Cell Above</a></li>
115 <li id="insert_cell_below"><a href="#">Insert Cell Below</a></li>
114 <li id="insert_cell_below"><a href="#">Insert Cell Below</a></li>
116 </ul>
115 </ul>
117 </li>
116 </li>
118 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
117 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
119 <ul class="dropdown-menu">
118 <ul class="dropdown-menu">
120 <li id="run_cell"><a href="#">Run</a></li>
119 <li id="run_cell"><a href="#">Run</a></li>
121 <li id="run_cell_in_place"><a href="#">Run in Place</a></li>
120 <li id="run_cell_in_place"><a href="#">Run in Place</a></li>
122 <li id="run_all_cells"><a href="#">Run All</a></li>
121 <li id="run_all_cells"><a href="#">Run All</a></li>
123 <li id="run_all_cells_above"><a href="#">Run All Above</a></li>
122 <li id="run_all_cells_above"><a href="#">Run All Above</a></li>
124 <li id="run_all_cells_below"><a href="#">Run All Below</a></li>
123 <li id="run_all_cells_below"><a href="#">Run All Below</a></li>
125 <li class="divider"></li>
124 <li class="divider"></li>
126 <li id="change_cell_type" class="dropdown-submenu"><a href="#">Cell Type</a>
125 <li id="change_cell_type" class="dropdown-submenu"><a href="#">Cell Type</a>
127 <ul class="dropdown-menu">
126 <ul class="dropdown-menu">
128 <li id="to_code"><a href="#">Code</a></li>
127 <li id="to_code"><a href="#">Code</a></li>
129 <li id="to_markdown"><a href="#">Markdown </a></li>
128 <li id="to_markdown"><a href="#">Markdown </a></li>
130 <li id="to_raw"><a href="#">Raw Text</a></li>
129 <li id="to_raw"><a href="#">Raw Text</a></li>
131 <li id="to_heading1"><a href="#">Heading 1</a></li>
130 <li id="to_heading1"><a href="#">Heading 1</a></li>
132 <li id="to_heading2"><a href="#">Heading 2</a></li>
131 <li id="to_heading2"><a href="#">Heading 2</a></li>
133 <li id="to_heading3"><a href="#">Heading 3</a></li>
132 <li id="to_heading3"><a href="#">Heading 3</a></li>
134 <li id="to_heading4"><a href="#">Heading 4</a></li>
133 <li id="to_heading4"><a href="#">Heading 4</a></li>
135 <li id="to_heading5"><a href="#">Heading 5</a></li>
134 <li id="to_heading5"><a href="#">Heading 5</a></li>
136 <li id="to_heading6"><a href="#">Heading 6</a></li>
135 <li id="to_heading6"><a href="#">Heading 6</a></li>
137 </ul>
136 </ul>
138 </li>
137 </li>
139 <li class="divider"></li>
138 <li class="divider"></li>
140 <li id="toggle_output"><a href="#">Toggle Current Output</a></li>
139 <li id="toggle_output"><a href="#">Toggle Current Output</a></li>
141 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
140 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
142 <ul class="dropdown-menu">
141 <ul class="dropdown-menu">
143 <li id="expand_all_output"><a href="#">Expand</a></li>
142 <li id="expand_all_output"><a href="#">Expand</a></li>
144 <li id="scroll_all_output"><a href="#">Scroll Long</a></li>
143 <li id="scroll_all_output"><a href="#">Scroll Long</a></li>
145 <li id="collapse_all_output"><a href="#">Collapse</a></li>
144 <li id="collapse_all_output"><a href="#">Collapse</a></li>
146 <li id="clear_all_output"><a href="#">Clear</a></li>
145 <li id="clear_all_output"><a href="#">Clear</a></li>
147 </ul>
146 </ul>
148 </li>
147 </li>
149 </ul>
148 </ul>
150 </li>
149 </li>
151 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
150 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
152 <ul class="dropdown-menu">
151 <ul class="dropdown-menu">
153 <li id="int_kernel"><a href="#">Interrupt</a></li>
152 <li id="int_kernel"><a href="#">Interrupt</a></li>
154 <li id="restart_kernel"><a href="#">Restart</a></li>
153 <li id="restart_kernel"><a href="#">Restart</a></li>
155 </ul>
154 </ul>
156 </li>
155 </li>
157 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
156 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
158 <ul class="dropdown-menu">
157 <ul class="dropdown-menu">
159 <li><a href="http://ipython.org/documentation.html" target="_blank">IPython Help</a></li>
158 <li><a href="http://ipython.org/documentation.html" target="_blank">IPython Help</a></li>
160 <li><a href="http://ipython.org/ipython-doc/stable/interactive/htmlnotebook.html" target="_blank">Notebook Help</a></li>
159 <li><a href="http://ipython.org/ipython-doc/stable/interactive/htmlnotebook.html" target="_blank">Notebook Help</a></li>
161 <li id="keyboard_shortcuts"><a href="#">Keyboard Shortcuts</a></li>
160 <li id="keyboard_shortcuts"><a href="#">Keyboard Shortcuts</a></li>
162 <li class="divider"></li>
161 <li class="divider"></li>
163 <li><a href="http://docs.python.org" target="_blank">Python</a></li>
162 <li><a href="http://docs.python.org" target="_blank">Python</a></li>
164 <li><a href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a></li>
163 <li><a href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a></li>
165 <li><a href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a></li>
164 <li><a href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a></li>
166 <li><a href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a></li>
165 <li><a href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a></li>
167 <li><a href="http://matplotlib.sourceforge.net/" target="_blank">Matplotlib</a></li>
166 <li><a href="http://matplotlib.sourceforge.net/" target="_blank">Matplotlib</a></li>
168 </ul>
167 </ul>
169 </li>
168 </li>
170 </ul>
169 </ul>
171 <div id="notification_area"></div>
170 <div id="notification_area"></div>
172 </div>
171 </div>
173 </div>
172 </div>
174 </div>
173 </div>
175 </div>
174 </div>
176 <div id="maintoolbar" class="navbar">
175 <div id="maintoolbar" class="navbar">
177 <div class="toolbar-inner navbar-inner navbar-nobg">
176 <div class="toolbar-inner navbar-inner navbar-nobg">
178 <div id="maintoolbar-container" class="container"></div>
177 <div id="maintoolbar-container" class="container"></div>
179 </div>
178 </div>
180 </div>
179 </div>
181 </div>
180 </div>
182
181
183 <div id="ipython-main-app">
182 <div id="ipython-main-app">
184
183
185 <div id="notebook_panel">
184 <div id="notebook_panel">
186 <div id="notebook"></div>
185 <div id="notebook"></div>
187 <div id="pager_splitter"></div>
186 <div id="pager_splitter"></div>
188 <div id="pager">
187 <div id="pager">
189 <div id='pager_button_area'>
188 <div id='pager_button_area'>
190 </div>
189 </div>
191 <div id="pager-container" class="container"></div>
190 <div id="pager-container" class="container"></div>
192 </div>
191 </div>
193 </div>
192 </div>
194
193
195 </div>
194 </div>
196 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
195 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
197
196
198
197
199 {% endblock %}
198 {% endblock %}
200
199
201
200
202 {% block script %}
201 {% block script %}
203
202
204 {{super()}}
203 {{super()}}
205
204
206 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
205 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
207 <script type="text/javascript">
206 <script type="text/javascript">
208 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js") }}";
207 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js") }}";
209 </script>
208 </script>
210 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
209 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
211 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
210 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
212 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
211 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
213 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
212 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
214 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
213 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
215 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
214 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
216 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
215 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
217 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
216 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
218 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
217 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
219 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
218 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
220 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
219 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
221 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
220 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
222 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
221 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
223
222
224 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
223 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
225
224
226 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
225 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
227
226
228 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
227 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
229 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
228 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
230 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
229 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
231 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
230 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
232 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
231 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
233 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
232 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
234 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
233 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
235 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
234 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
236 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
235 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
237 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
236 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
238 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
237 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
239 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
238 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
240 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
239 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
241 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
240 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
242 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
241 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
243 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
242 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
244 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
243 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
245 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
244 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
246 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
245 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
247 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
246 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
248 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
247 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
249 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
248 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
250 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
249 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
251 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
250 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
252
251
253 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
252 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
254
253
255 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
254 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
256 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
255 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
257
256
258 {% endblock %}
257 {% endblock %}
@@ -1,92 +1,91 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block title %}IPython Dashboard{% endblock %}
3 {% block title %}IPython Dashboard{% endblock %}
4
4
5
5
6 {% block stylesheet %}
6 {% block stylesheet %}
7 {{super()}}
7 {{super()}}
8 <link rel="stylesheet" href="{{ static_url("tree/css/override.css") }}" type="text/css" />
8 <link rel="stylesheet" href="{{ static_url("tree/css/override.css") }}" type="text/css" />
9 {% endblock %}
9 {% endblock %}
10
10
11 {% block params %}
11 {% block params %}
12
12
13 data-project={{project}}
13 data-project={{project}}
14 data-base-project-url={{base_project_url}}
14 data-base-project-url={{base_project_url}}
15 data-base-kernel-url={{base_kernel_url}}
15 data-base-kernel-url={{base_kernel_url}}
16 data-read-only={{read_only}}
17
16
18 {% endblock %}
17 {% endblock %}
19
18
20
19
21 {% block site %}
20 {% block site %}
22
21
23 <div id="ipython-main-app" class="container">
22 <div id="ipython-main-app" class="container">
24
23
25 <div id="tabs" class="tabbable">
24 <div id="tabs" class="tabbable">
26 <ul class="nav nav-tabs" id="tabs">
25 <ul class="nav nav-tabs" id="tabs">
27 <li class="active"><a href="#notebooks" data-toggle="tab">Notebooks</a></li>
26 <li class="active"><a href="#notebooks" data-toggle="tab">Notebooks</a></li>
28 <li><a href="#clusters" data-toggle="tab">Clusters</a></li>
27 <li><a href="#clusters" data-toggle="tab">Clusters</a></li>
29 </ul>
28 </ul>
30
29
31 <div class="tab-content">
30 <div class="tab-content">
32 <div id="notebooks" class="tab-pane active">
31 <div id="notebooks" class="tab-pane active">
33 {% if logged_in or not read_only %}
32 {% if logged_in %}
34 <div id="notebook_toolbar">
33 <div id="notebook_toolbar">
35 <form id='alternate_upload' class='alternate_upload' >
34 <form id='alternate_upload' class='alternate_upload' >
36 <span id="drag_info" style="position:absolute" >
35 <span id="drag_info" style="position:absolute" >
37 To import a notebook, drag the file onto the listing below or <strong>click here</strong>.
36 To import a notebook, drag the file onto the listing below or <strong>click here</strong>.
38 </span>
37 </span>
39 <input type="file" name="datafile" class="fileinput" multiple='multiple'>
38 <input type="file" name="datafile" class="fileinput" multiple='multiple'>
40 </form>
39 </form>
41 <span id="notebook_buttons">
40 <span id="notebook_buttons">
42 <button id="refresh_notebook_list" title="Refresh notebook list" class="btn btn-small">Refresh</button>
41 <button id="refresh_notebook_list" title="Refresh notebook list" class="btn btn-small">Refresh</button>
43 <button id="new_notebook" title="Create new notebook" class="btn btn-small">New Notebook</button>
42 <button id="new_notebook" title="Create new notebook" class="btn btn-small">New Notebook</button>
44 </span>
43 </span>
45 </div>
44 </div>
46 {% endif %}
45 {% endif %}
47
46
48 <div id="notebook_list">
47 <div id="notebook_list">
49 <div id="notebook_list_header" class="row-fluid list_header">
48 <div id="notebook_list_header" class="row-fluid list_header">
50 <div id="project_name">
49 <div id="project_name">
51 <ul class="breadcrumb">
50 <ul class="breadcrumb">
52 {% for component in project_component %}
51 {% for component in project_component %}
53 <li>{{component}} <span>/</span></li>
52 <li>{{component}} <span>/</span></li>
54 {% endfor %}
53 {% endfor %}
55 </ul>
54 </ul>
56 </div>
55 </div>
57 </div>
56 </div>
58 </div>
57 </div>
59 </div>
58 </div>
60
59
61 <div id="clusters" class="tab-pane">
60 <div id="clusters" class="tab-pane">
62
61
63 <div id="cluster_toolbar">
62 <div id="cluster_toolbar">
64 <span id="cluster_list_info">IPython parallel computing clusters</span>
63 <span id="cluster_list_info">IPython parallel computing clusters</span>
65
64
66 <span id="cluster_buttons">
65 <span id="cluster_buttons">
67 <button id="refresh_cluster_list" title="Refresh cluster list" class="btn btn-small">Refresh</button>
66 <button id="refresh_cluster_list" title="Refresh cluster list" class="btn btn-small">Refresh</button>
68 </span>
67 </span>
69 </div>
68 </div>
70
69
71 <div id="cluster_list">
70 <div id="cluster_list">
72 <div id="cluster_list_header" class="row-fluid list_header">
71 <div id="cluster_list_header" class="row-fluid list_header">
73 <span class="profile_col span4">profile</span>
72 <span class="profile_col span4">profile</span>
74 <span class="status_col span3">status</span>
73 <span class="status_col span3">status</span>
75 <span class="engines_col span3" title="Enter the number of engines to start or empty for default"># of engines</span>
74 <span class="engines_col span3" title="Enter the number of engines to start or empty for default"># of engines</span>
76 <span class="action_col span2">action</span>
75 <span class="action_col span2">action</span>
77 </div>
76 </div>
78 </div>
77 </div>
79 </div>
78 </div>
80 </div>
79 </div>
81
80
82 </div>
81 </div>
83
82
84 {% endblock %}
83 {% endblock %}
85
84
86 {% block script %}
85 {% block script %}
87 {{super()}}
86 {{super()}}
88 <script src="{{static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
87 <script src="{{static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
89 <script src="{{static_url("tree/js/notebooklist.js") }}" type="text/javascript" charset="utf-8"></script>
88 <script src="{{static_url("tree/js/notebooklist.js") }}" type="text/javascript" charset="utf-8"></script>
90 <script src="{{static_url("tree/js/clusterlist.js") }}" type="text/javascript" charset="utf-8"></script>
89 <script src="{{static_url("tree/js/clusterlist.js") }}" type="text/javascript" charset="utf-8"></script>
91 <script src="{{static_url("tree/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
90 <script src="{{static_url("tree/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
92 {% endblock %}
91 {% endblock %}
@@ -1,41 +1,42 b''
1 """Tornado handlers for the tree view.
1 """Tornado handlers for the tree view.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from ..base.handlers import IPythonHandler, authenticate_unless_readonly
19 from tornado import web
20 from ..base.handlers import IPythonHandler
20
21
21 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
22 # Handlers
23 # Handlers
23 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
24
25
25
26
26 class ProjectDashboardHandler(IPythonHandler):
27 class ProjectDashboardHandler(IPythonHandler):
27
28
28 @authenticate_unless_readonly
29 @web.authenticated
29 def get(self):
30 def get(self):
30 self.write(self.render_template('tree.html',
31 self.write(self.render_template('tree.html',
31 project=self.project,
32 project=self.project,
32 project_component=self.project.split('/'),
33 project_component=self.project.split('/'),
33 ))
34 ))
34
35
35
36
36 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
37 # URL to handler mappings
38 # URL to handler mappings
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39
40
40
41
41 default_handlers = [(r"/", ProjectDashboardHandler)] No newline at end of file
42 default_handlers = [(r"/", ProjectDashboardHandler)]
General Comments 0
You need to be logged in to leave comments. Login now