##// END OF EJS Templates
remove notebook read-only view...
MinRK -
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,2037 +1,2032 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 that.save_notebook();
367 that.save_notebook();
372 return "Autosave in progress, latest changes may be lost.";
368 return "Autosave in progress, latest changes may be lost.";
373 } else {
369 } else {
374 return "Unsaved changes will be lost.";
370 return "Unsaved changes will be lost.";
375 }
371 }
376 };
372 };
377 // Null is the *only* return value that will make the browser not
373 // Null is the *only* return value that will make the browser not
378 // pop up the "don't leave" dialog.
374 // pop up the "don't leave" dialog.
379 return null;
375 return null;
380 };
376 };
381 };
377 };
382
378
383 /**
379 /**
384 * Set the dirty flag, and trigger the set_dirty.Notebook event
380 * Set the dirty flag, and trigger the set_dirty.Notebook event
385 *
381 *
386 * @method set_dirty
382 * @method set_dirty
387 */
383 */
388 Notebook.prototype.set_dirty = function (value) {
384 Notebook.prototype.set_dirty = function (value) {
389 if (value === undefined) {
385 if (value === undefined) {
390 value = true;
386 value = true;
391 }
387 }
392 if (this.dirty == value) {
388 if (this.dirty == value) {
393 return;
389 return;
394 }
390 }
395 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
391 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
396 };
392 };
397
393
398 /**
394 /**
399 * Scroll the top of the page to a given cell.
395 * Scroll the top of the page to a given cell.
400 *
396 *
401 * @method scroll_to_cell
397 * @method scroll_to_cell
402 * @param {Number} cell_number An index of the cell to view
398 * @param {Number} cell_number An index of the cell to view
403 * @param {Number} time Animation time in milliseconds
399 * @param {Number} time Animation time in milliseconds
404 * @return {Number} Pixel offset from the top of the container
400 * @return {Number} Pixel offset from the top of the container
405 */
401 */
406 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
402 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
407 var cells = this.get_cells();
403 var cells = this.get_cells();
408 var time = time || 0;
404 var time = time || 0;
409 cell_number = Math.min(cells.length-1,cell_number);
405 cell_number = Math.min(cells.length-1,cell_number);
410 cell_number = Math.max(0 ,cell_number);
406 cell_number = Math.max(0 ,cell_number);
411 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
407 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
412 this.element.animate({scrollTop:scroll_value}, time);
408 this.element.animate({scrollTop:scroll_value}, time);
413 return scroll_value;
409 return scroll_value;
414 };
410 };
415
411
416 /**
412 /**
417 * Scroll to the bottom of the page.
413 * Scroll to the bottom of the page.
418 *
414 *
419 * @method scroll_to_bottom
415 * @method scroll_to_bottom
420 */
416 */
421 Notebook.prototype.scroll_to_bottom = function () {
417 Notebook.prototype.scroll_to_bottom = function () {
422 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
418 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
423 };
419 };
424
420
425 /**
421 /**
426 * Scroll to the top of the page.
422 * Scroll to the top of the page.
427 *
423 *
428 * @method scroll_to_top
424 * @method scroll_to_top
429 */
425 */
430 Notebook.prototype.scroll_to_top = function () {
426 Notebook.prototype.scroll_to_top = function () {
431 this.element.animate({scrollTop:0}, 0);
427 this.element.animate({scrollTop:0}, 0);
432 };
428 };
433
429
434
430
435 // Cell indexing, retrieval, etc.
431 // Cell indexing, retrieval, etc.
436
432
437 /**
433 /**
438 * Get all cell elements in the notebook.
434 * Get all cell elements in the notebook.
439 *
435 *
440 * @method get_cell_elements
436 * @method get_cell_elements
441 * @return {jQuery} A selector of all cell elements
437 * @return {jQuery} A selector of all cell elements
442 */
438 */
443 Notebook.prototype.get_cell_elements = function () {
439 Notebook.prototype.get_cell_elements = function () {
444 return this.container.children("div.cell");
440 return this.container.children("div.cell");
445 };
441 };
446
442
447 /**
443 /**
448 * Get a particular cell element.
444 * Get a particular cell element.
449 *
445 *
450 * @method get_cell_element
446 * @method get_cell_element
451 * @param {Number} index An index of a cell to select
447 * @param {Number} index An index of a cell to select
452 * @return {jQuery} A selector of the given cell.
448 * @return {jQuery} A selector of the given cell.
453 */
449 */
454 Notebook.prototype.get_cell_element = function (index) {
450 Notebook.prototype.get_cell_element = function (index) {
455 var result = null;
451 var result = null;
456 var e = this.get_cell_elements().eq(index);
452 var e = this.get_cell_elements().eq(index);
457 if (e.length !== 0) {
453 if (e.length !== 0) {
458 result = e;
454 result = e;
459 }
455 }
460 return result;
456 return result;
461 };
457 };
462
458
463 /**
459 /**
464 * Count the cells in this notebook.
460 * Count the cells in this notebook.
465 *
461 *
466 * @method ncells
462 * @method ncells
467 * @return {Number} The number of cells in this notebook
463 * @return {Number} The number of cells in this notebook
468 */
464 */
469 Notebook.prototype.ncells = function () {
465 Notebook.prototype.ncells = function () {
470 return this.get_cell_elements().length;
466 return this.get_cell_elements().length;
471 };
467 };
472
468
473 /**
469 /**
474 * Get all Cell objects in this notebook.
470 * Get all Cell objects in this notebook.
475 *
471 *
476 * @method get_cells
472 * @method get_cells
477 * @return {Array} This notebook's Cell objects
473 * @return {Array} This notebook's Cell objects
478 */
474 */
479 // TODO: we are often calling cells as cells()[i], which we should optimize
475 // TODO: we are often calling cells as cells()[i], which we should optimize
480 // to cells(i) or a new method.
476 // to cells(i) or a new method.
481 Notebook.prototype.get_cells = function () {
477 Notebook.prototype.get_cells = function () {
482 return this.get_cell_elements().toArray().map(function (e) {
478 return this.get_cell_elements().toArray().map(function (e) {
483 return $(e).data("cell");
479 return $(e).data("cell");
484 });
480 });
485 };
481 };
486
482
487 /**
483 /**
488 * Get a Cell object from this notebook.
484 * Get a Cell object from this notebook.
489 *
485 *
490 * @method get_cell
486 * @method get_cell
491 * @param {Number} index An index of a cell to retrieve
487 * @param {Number} index An index of a cell to retrieve
492 * @return {Cell} A particular cell
488 * @return {Cell} A particular cell
493 */
489 */
494 Notebook.prototype.get_cell = function (index) {
490 Notebook.prototype.get_cell = function (index) {
495 var result = null;
491 var result = null;
496 var ce = this.get_cell_element(index);
492 var ce = this.get_cell_element(index);
497 if (ce !== null) {
493 if (ce !== null) {
498 result = ce.data('cell');
494 result = ce.data('cell');
499 }
495 }
500 return result;
496 return result;
501 }
497 }
502
498
503 /**
499 /**
504 * Get the cell below a given cell.
500 * Get the cell below a given cell.
505 *
501 *
506 * @method get_next_cell
502 * @method get_next_cell
507 * @param {Cell} cell The provided cell
503 * @param {Cell} cell The provided cell
508 * @return {Cell} The next cell
504 * @return {Cell} The next cell
509 */
505 */
510 Notebook.prototype.get_next_cell = function (cell) {
506 Notebook.prototype.get_next_cell = function (cell) {
511 var result = null;
507 var result = null;
512 var index = this.find_cell_index(cell);
508 var index = this.find_cell_index(cell);
513 if (this.is_valid_cell_index(index+1)) {
509 if (this.is_valid_cell_index(index+1)) {
514 result = this.get_cell(index+1);
510 result = this.get_cell(index+1);
515 }
511 }
516 return result;
512 return result;
517 }
513 }
518
514
519 /**
515 /**
520 * Get the cell above a given cell.
516 * Get the cell above a given cell.
521 *
517 *
522 * @method get_prev_cell
518 * @method get_prev_cell
523 * @param {Cell} cell The provided cell
519 * @param {Cell} cell The provided cell
524 * @return {Cell} The previous cell
520 * @return {Cell} The previous cell
525 */
521 */
526 Notebook.prototype.get_prev_cell = function (cell) {
522 Notebook.prototype.get_prev_cell = function (cell) {
527 // TODO: off-by-one
523 // TODO: off-by-one
528 // nb.get_prev_cell(nb.get_cell(1)) is null
524 // nb.get_prev_cell(nb.get_cell(1)) is null
529 var result = null;
525 var result = null;
530 var index = this.find_cell_index(cell);
526 var index = this.find_cell_index(cell);
531 if (index !== null && index > 1) {
527 if (index !== null && index > 1) {
532 result = this.get_cell(index-1);
528 result = this.get_cell(index-1);
533 }
529 }
534 return result;
530 return result;
535 }
531 }
536
532
537 /**
533 /**
538 * Get the numeric index of a given cell.
534 * Get the numeric index of a given cell.
539 *
535 *
540 * @method find_cell_index
536 * @method find_cell_index
541 * @param {Cell} cell The provided cell
537 * @param {Cell} cell The provided cell
542 * @return {Number} The cell's numeric index
538 * @return {Number} The cell's numeric index
543 */
539 */
544 Notebook.prototype.find_cell_index = function (cell) {
540 Notebook.prototype.find_cell_index = function (cell) {
545 var result = null;
541 var result = null;
546 this.get_cell_elements().filter(function (index) {
542 this.get_cell_elements().filter(function (index) {
547 if ($(this).data("cell") === cell) {
543 if ($(this).data("cell") === cell) {
548 result = index;
544 result = index;
549 };
545 };
550 });
546 });
551 return result;
547 return result;
552 };
548 };
553
549
554 /**
550 /**
555 * Get a given index , or the selected index if none is provided.
551 * Get a given index , or the selected index if none is provided.
556 *
552 *
557 * @method index_or_selected
553 * @method index_or_selected
558 * @param {Number} index A cell's index
554 * @param {Number} index A cell's index
559 * @return {Number} The given index, or selected index if none is provided.
555 * @return {Number} The given index, or selected index if none is provided.
560 */
556 */
561 Notebook.prototype.index_or_selected = function (index) {
557 Notebook.prototype.index_or_selected = function (index) {
562 var i;
558 var i;
563 if (index === undefined || index === null) {
559 if (index === undefined || index === null) {
564 i = this.get_selected_index();
560 i = this.get_selected_index();
565 if (i === null) {
561 if (i === null) {
566 i = 0;
562 i = 0;
567 }
563 }
568 } else {
564 } else {
569 i = index;
565 i = index;
570 }
566 }
571 return i;
567 return i;
572 };
568 };
573
569
574 /**
570 /**
575 * Get the currently selected cell.
571 * Get the currently selected cell.
576 * @method get_selected_cell
572 * @method get_selected_cell
577 * @return {Cell} The selected cell
573 * @return {Cell} The selected cell
578 */
574 */
579 Notebook.prototype.get_selected_cell = function () {
575 Notebook.prototype.get_selected_cell = function () {
580 var index = this.get_selected_index();
576 var index = this.get_selected_index();
581 return this.get_cell(index);
577 return this.get_cell(index);
582 };
578 };
583
579
584 /**
580 /**
585 * Check whether a cell index is valid.
581 * Check whether a cell index is valid.
586 *
582 *
587 * @method is_valid_cell_index
583 * @method is_valid_cell_index
588 * @param {Number} index A cell index
584 * @param {Number} index A cell index
589 * @return True if the index is valid, false otherwise
585 * @return True if the index is valid, false otherwise
590 */
586 */
591 Notebook.prototype.is_valid_cell_index = function (index) {
587 Notebook.prototype.is_valid_cell_index = function (index) {
592 if (index !== null && index >= 0 && index < this.ncells()) {
588 if (index !== null && index >= 0 && index < this.ncells()) {
593 return true;
589 return true;
594 } else {
590 } else {
595 return false;
591 return false;
596 };
592 };
597 }
593 }
598
594
599 /**
595 /**
600 * Get the index of the currently selected cell.
596 * Get the index of the currently selected cell.
601
597
602 * @method get_selected_index
598 * @method get_selected_index
603 * @return {Number} The selected cell's numeric index
599 * @return {Number} The selected cell's numeric index
604 */
600 */
605 Notebook.prototype.get_selected_index = function () {
601 Notebook.prototype.get_selected_index = function () {
606 var result = null;
602 var result = null;
607 this.get_cell_elements().filter(function (index) {
603 this.get_cell_elements().filter(function (index) {
608 if ($(this).data("cell").selected === true) {
604 if ($(this).data("cell").selected === true) {
609 result = index;
605 result = index;
610 };
606 };
611 });
607 });
612 return result;
608 return result;
613 };
609 };
614
610
615
611
616 // Cell selection.
612 // Cell selection.
617
613
618 /**
614 /**
619 * Programmatically select a cell.
615 * Programmatically select a cell.
620 *
616 *
621 * @method select
617 * @method select
622 * @param {Number} index A cell's index
618 * @param {Number} index A cell's index
623 * @return {Notebook} This notebook
619 * @return {Notebook} This notebook
624 */
620 */
625 Notebook.prototype.select = function (index) {
621 Notebook.prototype.select = function (index) {
626 if (this.is_valid_cell_index(index)) {
622 if (this.is_valid_cell_index(index)) {
627 var sindex = this.get_selected_index()
623 var sindex = this.get_selected_index()
628 if (sindex !== null && index !== sindex) {
624 if (sindex !== null && index !== sindex) {
629 this.get_cell(sindex).unselect();
625 this.get_cell(sindex).unselect();
630 };
626 };
631 var cell = this.get_cell(index);
627 var cell = this.get_cell(index);
632 cell.select();
628 cell.select();
633 if (cell.cell_type === 'heading') {
629 if (cell.cell_type === 'heading') {
634 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
630 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
635 {'cell_type':cell.cell_type,level:cell.level}
631 {'cell_type':cell.cell_type,level:cell.level}
636 );
632 );
637 } else {
633 } else {
638 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
634 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
639 {'cell_type':cell.cell_type}
635 {'cell_type':cell.cell_type}
640 );
636 );
641 };
637 };
642 };
638 };
643 return this;
639 return this;
644 };
640 };
645
641
646 /**
642 /**
647 * Programmatically select the next cell.
643 * Programmatically select the next cell.
648 *
644 *
649 * @method select_next
645 * @method select_next
650 * @return {Notebook} This notebook
646 * @return {Notebook} This notebook
651 */
647 */
652 Notebook.prototype.select_next = function () {
648 Notebook.prototype.select_next = function () {
653 var index = this.get_selected_index();
649 var index = this.get_selected_index();
654 this.select(index+1);
650 this.select(index+1);
655 return this;
651 return this;
656 };
652 };
657
653
658 /**
654 /**
659 * Programmatically select the previous cell.
655 * Programmatically select the previous cell.
660 *
656 *
661 * @method select_prev
657 * @method select_prev
662 * @return {Notebook} This notebook
658 * @return {Notebook} This notebook
663 */
659 */
664 Notebook.prototype.select_prev = function () {
660 Notebook.prototype.select_prev = function () {
665 var index = this.get_selected_index();
661 var index = this.get_selected_index();
666 this.select(index-1);
662 this.select(index-1);
667 return this;
663 return this;
668 };
664 };
669
665
670
666
671 // Cell movement
667 // Cell movement
672
668
673 /**
669 /**
674 * Move given (or selected) cell up and select it.
670 * Move given (or selected) cell up and select it.
675 *
671 *
676 * @method move_cell_up
672 * @method move_cell_up
677 * @param [index] {integer} cell index
673 * @param [index] {integer} cell index
678 * @return {Notebook} This notebook
674 * @return {Notebook} This notebook
679 **/
675 **/
680 Notebook.prototype.move_cell_up = function (index) {
676 Notebook.prototype.move_cell_up = function (index) {
681 var i = this.index_or_selected(index);
677 var i = this.index_or_selected(index);
682 if (this.is_valid_cell_index(i) && i > 0) {
678 if (this.is_valid_cell_index(i) && i > 0) {
683 var pivot = this.get_cell_element(i-1);
679 var pivot = this.get_cell_element(i-1);
684 var tomove = this.get_cell_element(i);
680 var tomove = this.get_cell_element(i);
685 if (pivot !== null && tomove !== null) {
681 if (pivot !== null && tomove !== null) {
686 tomove.detach();
682 tomove.detach();
687 pivot.before(tomove);
683 pivot.before(tomove);
688 this.select(i-1);
684 this.select(i-1);
689 };
685 };
690 this.set_dirty(true);
686 this.set_dirty(true);
691 };
687 };
692 return this;
688 return this;
693 };
689 };
694
690
695
691
696 /**
692 /**
697 * Move given (or selected) cell down and select it
693 * Move given (or selected) cell down and select it
698 *
694 *
699 * @method move_cell_down
695 * @method move_cell_down
700 * @param [index] {integer} cell index
696 * @param [index] {integer} cell index
701 * @return {Notebook} This notebook
697 * @return {Notebook} This notebook
702 **/
698 **/
703 Notebook.prototype.move_cell_down = function (index) {
699 Notebook.prototype.move_cell_down = function (index) {
704 var i = this.index_or_selected(index);
700 var i = this.index_or_selected(index);
705 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
701 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
706 var pivot = this.get_cell_element(i+1);
702 var pivot = this.get_cell_element(i+1);
707 var tomove = this.get_cell_element(i);
703 var tomove = this.get_cell_element(i);
708 if (pivot !== null && tomove !== null) {
704 if (pivot !== null && tomove !== null) {
709 tomove.detach();
705 tomove.detach();
710 pivot.after(tomove);
706 pivot.after(tomove);
711 this.select(i+1);
707 this.select(i+1);
712 };
708 };
713 };
709 };
714 this.set_dirty();
710 this.set_dirty();
715 return this;
711 return this;
716 };
712 };
717
713
718
714
719 // Insertion, deletion.
715 // Insertion, deletion.
720
716
721 /**
717 /**
722 * Delete a cell from the notebook.
718 * Delete a cell from the notebook.
723 *
719 *
724 * @method delete_cell
720 * @method delete_cell
725 * @param [index] A cell's numeric index
721 * @param [index] A cell's numeric index
726 * @return {Notebook} This notebook
722 * @return {Notebook} This notebook
727 */
723 */
728 Notebook.prototype.delete_cell = function (index) {
724 Notebook.prototype.delete_cell = function (index) {
729 var i = this.index_or_selected(index);
725 var i = this.index_or_selected(index);
730 var cell = this.get_selected_cell();
726 var cell = this.get_selected_cell();
731 this.undelete_backup = cell.toJSON();
727 this.undelete_backup = cell.toJSON();
732 $('#undelete_cell').removeClass('disabled');
728 $('#undelete_cell').removeClass('disabled');
733 if (this.is_valid_cell_index(i)) {
729 if (this.is_valid_cell_index(i)) {
734 var ce = this.get_cell_element(i);
730 var ce = this.get_cell_element(i);
735 ce.remove();
731 ce.remove();
736 if (i === (this.ncells())) {
732 if (i === (this.ncells())) {
737 this.select(i-1);
733 this.select(i-1);
738 this.undelete_index = i - 1;
734 this.undelete_index = i - 1;
739 this.undelete_below = true;
735 this.undelete_below = true;
740 } else {
736 } else {
741 this.select(i);
737 this.select(i);
742 this.undelete_index = i;
738 this.undelete_index = i;
743 this.undelete_below = false;
739 this.undelete_below = false;
744 };
740 };
745 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
741 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
746 this.set_dirty(true);
742 this.set_dirty(true);
747 };
743 };
748 return this;
744 return this;
749 };
745 };
750
746
751 /**
747 /**
752 * Insert a cell so that after insertion the cell is at given index.
748 * Insert a cell so that after insertion the cell is at given index.
753 *
749 *
754 * Similar to insert_above, but index parameter is mandatory
750 * Similar to insert_above, but index parameter is mandatory
755 *
751 *
756 * Index will be brought back into the accissible range [0,n]
752 * Index will be brought back into the accissible range [0,n]
757 *
753 *
758 * @method insert_cell_at_index
754 * @method insert_cell_at_index
759 * @param type {string} in ['code','markdown','heading']
755 * @param type {string} in ['code','markdown','heading']
760 * @param [index] {int} a valid index where to inser cell
756 * @param [index] {int} a valid index where to inser cell
761 *
757 *
762 * @return cell {cell|null} created cell or null
758 * @return cell {cell|null} created cell or null
763 **/
759 **/
764 Notebook.prototype.insert_cell_at_index = function(type, index){
760 Notebook.prototype.insert_cell_at_index = function(type, index){
765
761
766 var ncells = this.ncells();
762 var ncells = this.ncells();
767 var index = Math.min(index,ncells);
763 var index = Math.min(index,ncells);
768 index = Math.max(index,0);
764 index = Math.max(index,0);
769 var cell = null;
765 var cell = null;
770
766
771 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
767 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
772 if (type === 'code') {
768 if (type === 'code') {
773 cell = new IPython.CodeCell(this.kernel);
769 cell = new IPython.CodeCell(this.kernel);
774 cell.set_input_prompt();
770 cell.set_input_prompt();
775 } else if (type === 'markdown') {
771 } else if (type === 'markdown') {
776 cell = new IPython.MarkdownCell();
772 cell = new IPython.MarkdownCell();
777 } else if (type === 'raw') {
773 } else if (type === 'raw') {
778 cell = new IPython.RawCell();
774 cell = new IPython.RawCell();
779 } else if (type === 'heading') {
775 } else if (type === 'heading') {
780 cell = new IPython.HeadingCell();
776 cell = new IPython.HeadingCell();
781 }
777 }
782
778
783 if(this._insert_element_at_index(cell.element,index)){
779 if(this._insert_element_at_index(cell.element,index)){
784 cell.render();
780 cell.render();
785 this.select(this.find_cell_index(cell));
781 this.select(this.find_cell_index(cell));
786 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
782 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
787 this.set_dirty(true);
783 this.set_dirty(true);
788 }
784 }
789 }
785 }
790 return cell;
786 return cell;
791
787
792 };
788 };
793
789
794 /**
790 /**
795 * Insert an element at given cell index.
791 * Insert an element at given cell index.
796 *
792 *
797 * @method _insert_element_at_index
793 * @method _insert_element_at_index
798 * @param element {dom element} a cell element
794 * @param element {dom element} a cell element
799 * @param [index] {int} a valid index where to inser cell
795 * @param [index] {int} a valid index where to inser cell
800 * @private
796 * @private
801 *
797 *
802 * return true if everything whent fine.
798 * return true if everything whent fine.
803 **/
799 **/
804 Notebook.prototype._insert_element_at_index = function(element, index){
800 Notebook.prototype._insert_element_at_index = function(element, index){
805 if (element === undefined){
801 if (element === undefined){
806 return false;
802 return false;
807 }
803 }
808
804
809 var ncells = this.ncells();
805 var ncells = this.ncells();
810
806
811 if (ncells === 0) {
807 if (ncells === 0) {
812 // special case append if empty
808 // special case append if empty
813 this.element.find('div.end_space').before(element);
809 this.element.find('div.end_space').before(element);
814 } else if ( ncells === index ) {
810 } else if ( ncells === index ) {
815 // special case append it the end, but not empty
811 // special case append it the end, but not empty
816 this.get_cell_element(index-1).after(element);
812 this.get_cell_element(index-1).after(element);
817 } else if (this.is_valid_cell_index(index)) {
813 } else if (this.is_valid_cell_index(index)) {
818 // otherwise always somewhere to append to
814 // otherwise always somewhere to append to
819 this.get_cell_element(index).before(element);
815 this.get_cell_element(index).before(element);
820 } else {
816 } else {
821 return false;
817 return false;
822 }
818 }
823
819
824 if (this.undelete_index !== null && index <= this.undelete_index) {
820 if (this.undelete_index !== null && index <= this.undelete_index) {
825 this.undelete_index = this.undelete_index + 1;
821 this.undelete_index = this.undelete_index + 1;
826 this.set_dirty(true);
822 this.set_dirty(true);
827 }
823 }
828 return true;
824 return true;
829 };
825 };
830
826
831 /**
827 /**
832 * Insert a cell of given type above given index, or at top
828 * Insert a cell of given type above given index, or at top
833 * of notebook if index smaller than 0.
829 * of notebook if index smaller than 0.
834 *
830 *
835 * default index value is the one of currently selected cell
831 * default index value is the one of currently selected cell
836 *
832 *
837 * @method insert_cell_above
833 * @method insert_cell_above
838 * @param type {string} cell type
834 * @param type {string} cell type
839 * @param [index] {integer}
835 * @param [index] {integer}
840 *
836 *
841 * @return handle to created cell or null
837 * @return handle to created cell or null
842 **/
838 **/
843 Notebook.prototype.insert_cell_above = function (type, index) {
839 Notebook.prototype.insert_cell_above = function (type, index) {
844 index = this.index_or_selected(index);
840 index = this.index_or_selected(index);
845 return this.insert_cell_at_index(type, index);
841 return this.insert_cell_at_index(type, index);
846 };
842 };
847
843
848 /**
844 /**
849 * Insert a cell of given type below given index, or at bottom
845 * Insert a cell of given type below given index, or at bottom
850 * of notebook if index greater thatn number of cell
846 * of notebook if index greater thatn number of cell
851 *
847 *
852 * default index value is the one of currently selected cell
848 * default index value is the one of currently selected cell
853 *
849 *
854 * @method insert_cell_below
850 * @method insert_cell_below
855 * @param type {string} cell type
851 * @param type {string} cell type
856 * @param [index] {integer}
852 * @param [index] {integer}
857 *
853 *
858 * @return handle to created cell or null
854 * @return handle to created cell or null
859 *
855 *
860 **/
856 **/
861 Notebook.prototype.insert_cell_below = function (type, index) {
857 Notebook.prototype.insert_cell_below = function (type, index) {
862 index = this.index_or_selected(index);
858 index = this.index_or_selected(index);
863 return this.insert_cell_at_index(type, index+1);
859 return this.insert_cell_at_index(type, index+1);
864 };
860 };
865
861
866
862
867 /**
863 /**
868 * Insert cell at end of notebook
864 * Insert cell at end of notebook
869 *
865 *
870 * @method insert_cell_at_bottom
866 * @method insert_cell_at_bottom
871 * @param {String} type cell type
867 * @param {String} type cell type
872 *
868 *
873 * @return the added cell; or null
869 * @return the added cell; or null
874 **/
870 **/
875 Notebook.prototype.insert_cell_at_bottom = function (type){
871 Notebook.prototype.insert_cell_at_bottom = function (type){
876 var len = this.ncells();
872 var len = this.ncells();
877 return this.insert_cell_below(type,len-1);
873 return this.insert_cell_below(type,len-1);
878 };
874 };
879
875
880 /**
876 /**
881 * Turn a cell into a code cell.
877 * Turn a cell into a code cell.
882 *
878 *
883 * @method to_code
879 * @method to_code
884 * @param {Number} [index] A cell's index
880 * @param {Number} [index] A cell's index
885 */
881 */
886 Notebook.prototype.to_code = function (index) {
882 Notebook.prototype.to_code = function (index) {
887 var i = this.index_or_selected(index);
883 var i = this.index_or_selected(index);
888 if (this.is_valid_cell_index(i)) {
884 if (this.is_valid_cell_index(i)) {
889 var source_element = this.get_cell_element(i);
885 var source_element = this.get_cell_element(i);
890 var source_cell = source_element.data("cell");
886 var source_cell = source_element.data("cell");
891 if (!(source_cell instanceof IPython.CodeCell)) {
887 if (!(source_cell instanceof IPython.CodeCell)) {
892 var target_cell = this.insert_cell_below('code',i);
888 var target_cell = this.insert_cell_below('code',i);
893 var text = source_cell.get_text();
889 var text = source_cell.get_text();
894 if (text === source_cell.placeholder) {
890 if (text === source_cell.placeholder) {
895 text = '';
891 text = '';
896 }
892 }
897 target_cell.set_text(text);
893 target_cell.set_text(text);
898 // make this value the starting point, so that we can only undo
894 // make this value the starting point, so that we can only undo
899 // to this state, instead of a blank cell
895 // to this state, instead of a blank cell
900 target_cell.code_mirror.clearHistory();
896 target_cell.code_mirror.clearHistory();
901 source_element.remove();
897 source_element.remove();
902 this.set_dirty(true);
898 this.set_dirty(true);
903 };
899 };
904 };
900 };
905 };
901 };
906
902
907 /**
903 /**
908 * Turn a cell into a Markdown cell.
904 * Turn a cell into a Markdown cell.
909 *
905 *
910 * @method to_markdown
906 * @method to_markdown
911 * @param {Number} [index] A cell's index
907 * @param {Number} [index] A cell's index
912 */
908 */
913 Notebook.prototype.to_markdown = function (index) {
909 Notebook.prototype.to_markdown = function (index) {
914 var i = this.index_or_selected(index);
910 var i = this.index_or_selected(index);
915 if (this.is_valid_cell_index(i)) {
911 if (this.is_valid_cell_index(i)) {
916 var source_element = this.get_cell_element(i);
912 var source_element = this.get_cell_element(i);
917 var source_cell = source_element.data("cell");
913 var source_cell = source_element.data("cell");
918 if (!(source_cell instanceof IPython.MarkdownCell)) {
914 if (!(source_cell instanceof IPython.MarkdownCell)) {
919 var target_cell = this.insert_cell_below('markdown',i);
915 var target_cell = this.insert_cell_below('markdown',i);
920 var text = source_cell.get_text();
916 var text = source_cell.get_text();
921 if (text === source_cell.placeholder) {
917 if (text === source_cell.placeholder) {
922 text = '';
918 text = '';
923 };
919 };
924 // The edit must come before the set_text.
920 // The edit must come before the set_text.
925 target_cell.edit();
921 target_cell.edit();
926 target_cell.set_text(text);
922 target_cell.set_text(text);
927 // make this value the starting point, so that we can only undo
923 // make this value the starting point, so that we can only undo
928 // to this state, instead of a blank cell
924 // to this state, instead of a blank cell
929 target_cell.code_mirror.clearHistory();
925 target_cell.code_mirror.clearHistory();
930 source_element.remove();
926 source_element.remove();
931 this.set_dirty(true);
927 this.set_dirty(true);
932 };
928 };
933 };
929 };
934 };
930 };
935
931
936 /**
932 /**
937 * Turn a cell into a raw text cell.
933 * Turn a cell into a raw text cell.
938 *
934 *
939 * @method to_raw
935 * @method to_raw
940 * @param {Number} [index] A cell's index
936 * @param {Number} [index] A cell's index
941 */
937 */
942 Notebook.prototype.to_raw = function (index) {
938 Notebook.prototype.to_raw = function (index) {
943 var i = this.index_or_selected(index);
939 var i = this.index_or_selected(index);
944 if (this.is_valid_cell_index(i)) {
940 if (this.is_valid_cell_index(i)) {
945 var source_element = this.get_cell_element(i);
941 var source_element = this.get_cell_element(i);
946 var source_cell = source_element.data("cell");
942 var source_cell = source_element.data("cell");
947 var target_cell = null;
943 var target_cell = null;
948 if (!(source_cell instanceof IPython.RawCell)) {
944 if (!(source_cell instanceof IPython.RawCell)) {
949 target_cell = this.insert_cell_below('raw',i);
945 target_cell = this.insert_cell_below('raw',i);
950 var text = source_cell.get_text();
946 var text = source_cell.get_text();
951 if (text === source_cell.placeholder) {
947 if (text === source_cell.placeholder) {
952 text = '';
948 text = '';
953 };
949 };
954 // The edit must come before the set_text.
950 // The edit must come before the set_text.
955 target_cell.edit();
951 target_cell.edit();
956 target_cell.set_text(text);
952 target_cell.set_text(text);
957 // make this value the starting point, so that we can only undo
953 // make this value the starting point, so that we can only undo
958 // to this state, instead of a blank cell
954 // to this state, instead of a blank cell
959 target_cell.code_mirror.clearHistory();
955 target_cell.code_mirror.clearHistory();
960 source_element.remove();
956 source_element.remove();
961 this.set_dirty(true);
957 this.set_dirty(true);
962 };
958 };
963 };
959 };
964 };
960 };
965
961
966 /**
962 /**
967 * Turn a cell into a heading cell.
963 * Turn a cell into a heading cell.
968 *
964 *
969 * @method to_heading
965 * @method to_heading
970 * @param {Number} [index] A cell's index
966 * @param {Number} [index] A cell's index
971 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
967 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
972 */
968 */
973 Notebook.prototype.to_heading = function (index, level) {
969 Notebook.prototype.to_heading = function (index, level) {
974 level = level || 1;
970 level = level || 1;
975 var i = this.index_or_selected(index);
971 var i = this.index_or_selected(index);
976 if (this.is_valid_cell_index(i)) {
972 if (this.is_valid_cell_index(i)) {
977 var source_element = this.get_cell_element(i);
973 var source_element = this.get_cell_element(i);
978 var source_cell = source_element.data("cell");
974 var source_cell = source_element.data("cell");
979 var target_cell = null;
975 var target_cell = null;
980 if (source_cell instanceof IPython.HeadingCell) {
976 if (source_cell instanceof IPython.HeadingCell) {
981 source_cell.set_level(level);
977 source_cell.set_level(level);
982 } else {
978 } else {
983 target_cell = this.insert_cell_below('heading',i);
979 target_cell = this.insert_cell_below('heading',i);
984 var text = source_cell.get_text();
980 var text = source_cell.get_text();
985 if (text === source_cell.placeholder) {
981 if (text === source_cell.placeholder) {
986 text = '';
982 text = '';
987 };
983 };
988 // The edit must come before the set_text.
984 // The edit must come before the set_text.
989 target_cell.set_level(level);
985 target_cell.set_level(level);
990 target_cell.edit();
986 target_cell.edit();
991 target_cell.set_text(text);
987 target_cell.set_text(text);
992 // make this value the starting point, so that we can only undo
988 // make this value the starting point, so that we can only undo
993 // to this state, instead of a blank cell
989 // to this state, instead of a blank cell
994 target_cell.code_mirror.clearHistory();
990 target_cell.code_mirror.clearHistory();
995 source_element.remove();
991 source_element.remove();
996 this.set_dirty(true);
992 this.set_dirty(true);
997 };
993 };
998 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
994 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
999 {'cell_type':'heading',level:level}
995 {'cell_type':'heading',level:level}
1000 );
996 );
1001 };
997 };
1002 };
998 };
1003
999
1004
1000
1005 // Cut/Copy/Paste
1001 // Cut/Copy/Paste
1006
1002
1007 /**
1003 /**
1008 * Enable UI elements for pasting cells.
1004 * Enable UI elements for pasting cells.
1009 *
1005 *
1010 * @method enable_paste
1006 * @method enable_paste
1011 */
1007 */
1012 Notebook.prototype.enable_paste = function () {
1008 Notebook.prototype.enable_paste = function () {
1013 var that = this;
1009 var that = this;
1014 if (!this.paste_enabled) {
1010 if (!this.paste_enabled) {
1015 $('#paste_cell_replace').removeClass('disabled')
1011 $('#paste_cell_replace').removeClass('disabled')
1016 .on('click', function () {that.paste_cell_replace();});
1012 .on('click', function () {that.paste_cell_replace();});
1017 $('#paste_cell_above').removeClass('disabled')
1013 $('#paste_cell_above').removeClass('disabled')
1018 .on('click', function () {that.paste_cell_above();});
1014 .on('click', function () {that.paste_cell_above();});
1019 $('#paste_cell_below').removeClass('disabled')
1015 $('#paste_cell_below').removeClass('disabled')
1020 .on('click', function () {that.paste_cell_below();});
1016 .on('click', function () {that.paste_cell_below();});
1021 this.paste_enabled = true;
1017 this.paste_enabled = true;
1022 };
1018 };
1023 };
1019 };
1024
1020
1025 /**
1021 /**
1026 * Disable UI elements for pasting cells.
1022 * Disable UI elements for pasting cells.
1027 *
1023 *
1028 * @method disable_paste
1024 * @method disable_paste
1029 */
1025 */
1030 Notebook.prototype.disable_paste = function () {
1026 Notebook.prototype.disable_paste = function () {
1031 if (this.paste_enabled) {
1027 if (this.paste_enabled) {
1032 $('#paste_cell_replace').addClass('disabled').off('click');
1028 $('#paste_cell_replace').addClass('disabled').off('click');
1033 $('#paste_cell_above').addClass('disabled').off('click');
1029 $('#paste_cell_above').addClass('disabled').off('click');
1034 $('#paste_cell_below').addClass('disabled').off('click');
1030 $('#paste_cell_below').addClass('disabled').off('click');
1035 this.paste_enabled = false;
1031 this.paste_enabled = false;
1036 };
1032 };
1037 };
1033 };
1038
1034
1039 /**
1035 /**
1040 * Cut a cell.
1036 * Cut a cell.
1041 *
1037 *
1042 * @method cut_cell
1038 * @method cut_cell
1043 */
1039 */
1044 Notebook.prototype.cut_cell = function () {
1040 Notebook.prototype.cut_cell = function () {
1045 this.copy_cell();
1041 this.copy_cell();
1046 this.delete_cell();
1042 this.delete_cell();
1047 }
1043 }
1048
1044
1049 /**
1045 /**
1050 * Copy a cell.
1046 * Copy a cell.
1051 *
1047 *
1052 * @method copy_cell
1048 * @method copy_cell
1053 */
1049 */
1054 Notebook.prototype.copy_cell = function () {
1050 Notebook.prototype.copy_cell = function () {
1055 var cell = this.get_selected_cell();
1051 var cell = this.get_selected_cell();
1056 this.clipboard = cell.toJSON();
1052 this.clipboard = cell.toJSON();
1057 this.enable_paste();
1053 this.enable_paste();
1058 };
1054 };
1059
1055
1060 /**
1056 /**
1061 * Replace the selected cell with a cell in the clipboard.
1057 * Replace the selected cell with a cell in the clipboard.
1062 *
1058 *
1063 * @method paste_cell_replace
1059 * @method paste_cell_replace
1064 */
1060 */
1065 Notebook.prototype.paste_cell_replace = function () {
1061 Notebook.prototype.paste_cell_replace = function () {
1066 if (this.clipboard !== null && this.paste_enabled) {
1062 if (this.clipboard !== null && this.paste_enabled) {
1067 var cell_data = this.clipboard;
1063 var cell_data = this.clipboard;
1068 var new_cell = this.insert_cell_above(cell_data.cell_type);
1064 var new_cell = this.insert_cell_above(cell_data.cell_type);
1069 new_cell.fromJSON(cell_data);
1065 new_cell.fromJSON(cell_data);
1070 var old_cell = this.get_next_cell(new_cell);
1066 var old_cell = this.get_next_cell(new_cell);
1071 this.delete_cell(this.find_cell_index(old_cell));
1067 this.delete_cell(this.find_cell_index(old_cell));
1072 this.select(this.find_cell_index(new_cell));
1068 this.select(this.find_cell_index(new_cell));
1073 };
1069 };
1074 };
1070 };
1075
1071
1076 /**
1072 /**
1077 * Paste a cell from the clipboard above the selected cell.
1073 * Paste a cell from the clipboard above the selected cell.
1078 *
1074 *
1079 * @method paste_cell_above
1075 * @method paste_cell_above
1080 */
1076 */
1081 Notebook.prototype.paste_cell_above = function () {
1077 Notebook.prototype.paste_cell_above = function () {
1082 if (this.clipboard !== null && this.paste_enabled) {
1078 if (this.clipboard !== null && this.paste_enabled) {
1083 var cell_data = this.clipboard;
1079 var cell_data = this.clipboard;
1084 var new_cell = this.insert_cell_above(cell_data.cell_type);
1080 var new_cell = this.insert_cell_above(cell_data.cell_type);
1085 new_cell.fromJSON(cell_data);
1081 new_cell.fromJSON(cell_data);
1086 };
1082 };
1087 };
1083 };
1088
1084
1089 /**
1085 /**
1090 * Paste a cell from the clipboard below the selected cell.
1086 * Paste a cell from the clipboard below the selected cell.
1091 *
1087 *
1092 * @method paste_cell_below
1088 * @method paste_cell_below
1093 */
1089 */
1094 Notebook.prototype.paste_cell_below = function () {
1090 Notebook.prototype.paste_cell_below = function () {
1095 if (this.clipboard !== null && this.paste_enabled) {
1091 if (this.clipboard !== null && this.paste_enabled) {
1096 var cell_data = this.clipboard;
1092 var cell_data = this.clipboard;
1097 var new_cell = this.insert_cell_below(cell_data.cell_type);
1093 var new_cell = this.insert_cell_below(cell_data.cell_type);
1098 new_cell.fromJSON(cell_data);
1094 new_cell.fromJSON(cell_data);
1099 };
1095 };
1100 };
1096 };
1101
1097
1102 // Cell undelete
1098 // Cell undelete
1103
1099
1104 /**
1100 /**
1105 * Restore the most recently deleted cell.
1101 * Restore the most recently deleted cell.
1106 *
1102 *
1107 * @method undelete
1103 * @method undelete
1108 */
1104 */
1109 Notebook.prototype.undelete = function() {
1105 Notebook.prototype.undelete = function() {
1110 if (this.undelete_backup !== null && this.undelete_index !== null) {
1106 if (this.undelete_backup !== null && this.undelete_index !== null) {
1111 var current_index = this.get_selected_index();
1107 var current_index = this.get_selected_index();
1112 if (this.undelete_index < current_index) {
1108 if (this.undelete_index < current_index) {
1113 current_index = current_index + 1;
1109 current_index = current_index + 1;
1114 }
1110 }
1115 if (this.undelete_index >= this.ncells()) {
1111 if (this.undelete_index >= this.ncells()) {
1116 this.select(this.ncells() - 1);
1112 this.select(this.ncells() - 1);
1117 }
1113 }
1118 else {
1114 else {
1119 this.select(this.undelete_index);
1115 this.select(this.undelete_index);
1120 }
1116 }
1121 var cell_data = this.undelete_backup;
1117 var cell_data = this.undelete_backup;
1122 var new_cell = null;
1118 var new_cell = null;
1123 if (this.undelete_below) {
1119 if (this.undelete_below) {
1124 new_cell = this.insert_cell_below(cell_data.cell_type);
1120 new_cell = this.insert_cell_below(cell_data.cell_type);
1125 } else {
1121 } else {
1126 new_cell = this.insert_cell_above(cell_data.cell_type);
1122 new_cell = this.insert_cell_above(cell_data.cell_type);
1127 }
1123 }
1128 new_cell.fromJSON(cell_data);
1124 new_cell.fromJSON(cell_data);
1129 this.select(current_index);
1125 this.select(current_index);
1130 this.undelete_backup = null;
1126 this.undelete_backup = null;
1131 this.undelete_index = null;
1127 this.undelete_index = null;
1132 }
1128 }
1133 $('#undelete_cell').addClass('disabled');
1129 $('#undelete_cell').addClass('disabled');
1134 }
1130 }
1135
1131
1136 // Split/merge
1132 // Split/merge
1137
1133
1138 /**
1134 /**
1139 * Split the selected cell into two, at the cursor.
1135 * Split the selected cell into two, at the cursor.
1140 *
1136 *
1141 * @method split_cell
1137 * @method split_cell
1142 */
1138 */
1143 Notebook.prototype.split_cell = function () {
1139 Notebook.prototype.split_cell = function () {
1144 // Todo: implement spliting for other cell types.
1140 // Todo: implement spliting for other cell types.
1145 var cell = this.get_selected_cell();
1141 var cell = this.get_selected_cell();
1146 if (cell.is_splittable()) {
1142 if (cell.is_splittable()) {
1147 var texta = cell.get_pre_cursor();
1143 var texta = cell.get_pre_cursor();
1148 var textb = cell.get_post_cursor();
1144 var textb = cell.get_post_cursor();
1149 if (cell instanceof IPython.CodeCell) {
1145 if (cell instanceof IPython.CodeCell) {
1150 cell.set_text(texta);
1146 cell.set_text(texta);
1151 var new_cell = this.insert_cell_below('code');
1147 var new_cell = this.insert_cell_below('code');
1152 new_cell.set_text(textb);
1148 new_cell.set_text(textb);
1153 } else if (cell instanceof IPython.MarkdownCell) {
1149 } else if (cell instanceof IPython.MarkdownCell) {
1154 cell.set_text(texta);
1150 cell.set_text(texta);
1155 cell.render();
1151 cell.render();
1156 var new_cell = this.insert_cell_below('markdown');
1152 var new_cell = this.insert_cell_below('markdown');
1157 new_cell.edit(); // editor must be visible to call set_text
1153 new_cell.edit(); // editor must be visible to call set_text
1158 new_cell.set_text(textb);
1154 new_cell.set_text(textb);
1159 new_cell.render();
1155 new_cell.render();
1160 }
1156 }
1161 };
1157 };
1162 };
1158 };
1163
1159
1164 /**
1160 /**
1165 * Combine the selected cell into the cell above it.
1161 * Combine the selected cell into the cell above it.
1166 *
1162 *
1167 * @method merge_cell_above
1163 * @method merge_cell_above
1168 */
1164 */
1169 Notebook.prototype.merge_cell_above = function () {
1165 Notebook.prototype.merge_cell_above = function () {
1170 var index = this.get_selected_index();
1166 var index = this.get_selected_index();
1171 var cell = this.get_cell(index);
1167 var cell = this.get_cell(index);
1172 if (index > 0) {
1168 if (index > 0) {
1173 var upper_cell = this.get_cell(index-1);
1169 var upper_cell = this.get_cell(index-1);
1174 var upper_text = upper_cell.get_text();
1170 var upper_text = upper_cell.get_text();
1175 var text = cell.get_text();
1171 var text = cell.get_text();
1176 if (cell instanceof IPython.CodeCell) {
1172 if (cell instanceof IPython.CodeCell) {
1177 cell.set_text(upper_text+'\n'+text);
1173 cell.set_text(upper_text+'\n'+text);
1178 } else if (cell instanceof IPython.MarkdownCell) {
1174 } else if (cell instanceof IPython.MarkdownCell) {
1179 cell.edit();
1175 cell.edit();
1180 cell.set_text(upper_text+'\n'+text);
1176 cell.set_text(upper_text+'\n'+text);
1181 cell.render();
1177 cell.render();
1182 };
1178 };
1183 this.delete_cell(index-1);
1179 this.delete_cell(index-1);
1184 this.select(this.find_cell_index(cell));
1180 this.select(this.find_cell_index(cell));
1185 };
1181 };
1186 };
1182 };
1187
1183
1188 /**
1184 /**
1189 * Combine the selected cell into the cell below it.
1185 * Combine the selected cell into the cell below it.
1190 *
1186 *
1191 * @method merge_cell_below
1187 * @method merge_cell_below
1192 */
1188 */
1193 Notebook.prototype.merge_cell_below = function () {
1189 Notebook.prototype.merge_cell_below = function () {
1194 var index = this.get_selected_index();
1190 var index = this.get_selected_index();
1195 var cell = this.get_cell(index);
1191 var cell = this.get_cell(index);
1196 if (index < this.ncells()-1) {
1192 if (index < this.ncells()-1) {
1197 var lower_cell = this.get_cell(index+1);
1193 var lower_cell = this.get_cell(index+1);
1198 var lower_text = lower_cell.get_text();
1194 var lower_text = lower_cell.get_text();
1199 var text = cell.get_text();
1195 var text = cell.get_text();
1200 if (cell instanceof IPython.CodeCell) {
1196 if (cell instanceof IPython.CodeCell) {
1201 cell.set_text(text+'\n'+lower_text);
1197 cell.set_text(text+'\n'+lower_text);
1202 } else if (cell instanceof IPython.MarkdownCell) {
1198 } else if (cell instanceof IPython.MarkdownCell) {
1203 cell.edit();
1199 cell.edit();
1204 cell.set_text(text+'\n'+lower_text);
1200 cell.set_text(text+'\n'+lower_text);
1205 cell.render();
1201 cell.render();
1206 };
1202 };
1207 this.delete_cell(index+1);
1203 this.delete_cell(index+1);
1208 this.select(this.find_cell_index(cell));
1204 this.select(this.find_cell_index(cell));
1209 };
1205 };
1210 };
1206 };
1211
1207
1212
1208
1213 // Cell collapsing and output clearing
1209 // Cell collapsing and output clearing
1214
1210
1215 /**
1211 /**
1216 * Hide a cell's output.
1212 * Hide a cell's output.
1217 *
1213 *
1218 * @method collapse
1214 * @method collapse
1219 * @param {Number} index A cell's numeric index
1215 * @param {Number} index A cell's numeric index
1220 */
1216 */
1221 Notebook.prototype.collapse = function (index) {
1217 Notebook.prototype.collapse = function (index) {
1222 var i = this.index_or_selected(index);
1218 var i = this.index_or_selected(index);
1223 this.get_cell(i).collapse();
1219 this.get_cell(i).collapse();
1224 this.set_dirty(true);
1220 this.set_dirty(true);
1225 };
1221 };
1226
1222
1227 /**
1223 /**
1228 * Show a cell's output.
1224 * Show a cell's output.
1229 *
1225 *
1230 * @method expand
1226 * @method expand
1231 * @param {Number} index A cell's numeric index
1227 * @param {Number} index A cell's numeric index
1232 */
1228 */
1233 Notebook.prototype.expand = function (index) {
1229 Notebook.prototype.expand = function (index) {
1234 var i = this.index_or_selected(index);
1230 var i = this.index_or_selected(index);
1235 this.get_cell(i).expand();
1231 this.get_cell(i).expand();
1236 this.set_dirty(true);
1232 this.set_dirty(true);
1237 };
1233 };
1238
1234
1239 /** Toggle whether a cell's output is collapsed or expanded.
1235 /** Toggle whether a cell's output is collapsed or expanded.
1240 *
1236 *
1241 * @method toggle_output
1237 * @method toggle_output
1242 * @param {Number} index A cell's numeric index
1238 * @param {Number} index A cell's numeric index
1243 */
1239 */
1244 Notebook.prototype.toggle_output = function (index) {
1240 Notebook.prototype.toggle_output = function (index) {
1245 var i = this.index_or_selected(index);
1241 var i = this.index_or_selected(index);
1246 this.get_cell(i).toggle_output();
1242 this.get_cell(i).toggle_output();
1247 this.set_dirty(true);
1243 this.set_dirty(true);
1248 };
1244 };
1249
1245
1250 /**
1246 /**
1251 * Toggle a scrollbar for long cell outputs.
1247 * Toggle a scrollbar for long cell outputs.
1252 *
1248 *
1253 * @method toggle_output_scroll
1249 * @method toggle_output_scroll
1254 * @param {Number} index A cell's numeric index
1250 * @param {Number} index A cell's numeric index
1255 */
1251 */
1256 Notebook.prototype.toggle_output_scroll = function (index) {
1252 Notebook.prototype.toggle_output_scroll = function (index) {
1257 var i = this.index_or_selected(index);
1253 var i = this.index_or_selected(index);
1258 this.get_cell(i).toggle_output_scroll();
1254 this.get_cell(i).toggle_output_scroll();
1259 };
1255 };
1260
1256
1261 /**
1257 /**
1262 * Hide each code cell's output area.
1258 * Hide each code cell's output area.
1263 *
1259 *
1264 * @method collapse_all_output
1260 * @method collapse_all_output
1265 */
1261 */
1266 Notebook.prototype.collapse_all_output = function () {
1262 Notebook.prototype.collapse_all_output = function () {
1267 var ncells = this.ncells();
1263 var ncells = this.ncells();
1268 var cells = this.get_cells();
1264 var cells = this.get_cells();
1269 for (var i=0; i<ncells; i++) {
1265 for (var i=0; i<ncells; i++) {
1270 if (cells[i] instanceof IPython.CodeCell) {
1266 if (cells[i] instanceof IPython.CodeCell) {
1271 cells[i].output_area.collapse();
1267 cells[i].output_area.collapse();
1272 }
1268 }
1273 };
1269 };
1274 // this should not be set if the `collapse` key is removed from nbformat
1270 // this should not be set if the `collapse` key is removed from nbformat
1275 this.set_dirty(true);
1271 this.set_dirty(true);
1276 };
1272 };
1277
1273
1278 /**
1274 /**
1279 * Expand each code cell's output area, and add a scrollbar for long output.
1275 * Expand each code cell's output area, and add a scrollbar for long output.
1280 *
1276 *
1281 * @method scroll_all_output
1277 * @method scroll_all_output
1282 */
1278 */
1283 Notebook.prototype.scroll_all_output = function () {
1279 Notebook.prototype.scroll_all_output = function () {
1284 var ncells = this.ncells();
1280 var ncells = this.ncells();
1285 var cells = this.get_cells();
1281 var cells = this.get_cells();
1286 for (var i=0; i<ncells; i++) {
1282 for (var i=0; i<ncells; i++) {
1287 if (cells[i] instanceof IPython.CodeCell) {
1283 if (cells[i] instanceof IPython.CodeCell) {
1288 cells[i].output_area.expand();
1284 cells[i].output_area.expand();
1289 cells[i].output_area.scroll_if_long();
1285 cells[i].output_area.scroll_if_long();
1290 }
1286 }
1291 };
1287 };
1292 // this should not be set if the `collapse` key is removed from nbformat
1288 // this should not be set if the `collapse` key is removed from nbformat
1293 this.set_dirty(true);
1289 this.set_dirty(true);
1294 };
1290 };
1295
1291
1296 /**
1292 /**
1297 * Expand each code cell's output area, and remove scrollbars.
1293 * Expand each code cell's output area, and remove scrollbars.
1298 *
1294 *
1299 * @method expand_all_output
1295 * @method expand_all_output
1300 */
1296 */
1301 Notebook.prototype.expand_all_output = function () {
1297 Notebook.prototype.expand_all_output = function () {
1302 var ncells = this.ncells();
1298 var ncells = this.ncells();
1303 var cells = this.get_cells();
1299 var cells = this.get_cells();
1304 for (var i=0; i<ncells; i++) {
1300 for (var i=0; i<ncells; i++) {
1305 if (cells[i] instanceof IPython.CodeCell) {
1301 if (cells[i] instanceof IPython.CodeCell) {
1306 cells[i].output_area.expand();
1302 cells[i].output_area.expand();
1307 cells[i].output_area.unscroll_area();
1303 cells[i].output_area.unscroll_area();
1308 }
1304 }
1309 };
1305 };
1310 // this should not be set if the `collapse` key is removed from nbformat
1306 // this should not be set if the `collapse` key is removed from nbformat
1311 this.set_dirty(true);
1307 this.set_dirty(true);
1312 };
1308 };
1313
1309
1314 /**
1310 /**
1315 * Clear each code cell's output area.
1311 * Clear each code cell's output area.
1316 *
1312 *
1317 * @method clear_all_output
1313 * @method clear_all_output
1318 */
1314 */
1319 Notebook.prototype.clear_all_output = function () {
1315 Notebook.prototype.clear_all_output = function () {
1320 var ncells = this.ncells();
1316 var ncells = this.ncells();
1321 var cells = this.get_cells();
1317 var cells = this.get_cells();
1322 for (var i=0; i<ncells; i++) {
1318 for (var i=0; i<ncells; i++) {
1323 if (cells[i] instanceof IPython.CodeCell) {
1319 if (cells[i] instanceof IPython.CodeCell) {
1324 cells[i].clear_output(true,true,true);
1320 cells[i].clear_output(true,true,true);
1325 // Make all In[] prompts blank, as well
1321 // Make all In[] prompts blank, as well
1326 // TODO: make this configurable (via checkbox?)
1322 // TODO: make this configurable (via checkbox?)
1327 cells[i].set_input_prompt();
1323 cells[i].set_input_prompt();
1328 }
1324 }
1329 };
1325 };
1330 this.set_dirty(true);
1326 this.set_dirty(true);
1331 };
1327 };
1332
1328
1333
1329
1334 // Other cell functions: line numbers, ...
1330 // Other cell functions: line numbers, ...
1335
1331
1336 /**
1332 /**
1337 * Toggle line numbers in the selected cell's input area.
1333 * Toggle line numbers in the selected cell's input area.
1338 *
1334 *
1339 * @method cell_toggle_line_numbers
1335 * @method cell_toggle_line_numbers
1340 */
1336 */
1341 Notebook.prototype.cell_toggle_line_numbers = function() {
1337 Notebook.prototype.cell_toggle_line_numbers = function() {
1342 this.get_selected_cell().toggle_line_numbers();
1338 this.get_selected_cell().toggle_line_numbers();
1343 };
1339 };
1344
1340
1345 // Kernel related things
1341 // Kernel related things
1346
1342
1347 /**
1343 /**
1348 * Start a new kernel and set it on each code cell.
1344 * Start a new kernel and set it on each code cell.
1349 *
1345 *
1350 * @method start_kernel
1346 * @method start_kernel
1351 */
1347 */
1352 Notebook.prototype.start_kernel = function () {
1348 Notebook.prototype.start_kernel = function () {
1353 var base_url = $('body').data('baseKernelUrl') + "kernels";
1349 var base_url = $('body').data('baseKernelUrl') + "kernels";
1354 this.kernel = new IPython.Kernel(base_url);
1350 this.kernel = new IPython.Kernel(base_url);
1355 this.kernel.start(this.notebook_id);
1351 this.kernel.start(this.notebook_id);
1356 // Now that the kernel has been created, tell the CodeCells about it.
1352 // Now that the kernel has been created, tell the CodeCells about it.
1357 var ncells = this.ncells();
1353 var ncells = this.ncells();
1358 for (var i=0; i<ncells; i++) {
1354 for (var i=0; i<ncells; i++) {
1359 var cell = this.get_cell(i);
1355 var cell = this.get_cell(i);
1360 if (cell instanceof IPython.CodeCell) {
1356 if (cell instanceof IPython.CodeCell) {
1361 cell.set_kernel(this.kernel)
1357 cell.set_kernel(this.kernel)
1362 };
1358 };
1363 };
1359 };
1364 };
1360 };
1365
1361
1366 /**
1362 /**
1367 * Prompt the user to restart the IPython kernel.
1363 * Prompt the user to restart the IPython kernel.
1368 *
1364 *
1369 * @method restart_kernel
1365 * @method restart_kernel
1370 */
1366 */
1371 Notebook.prototype.restart_kernel = function () {
1367 Notebook.prototype.restart_kernel = function () {
1372 var that = this;
1368 var that = this;
1373 IPython.dialog.modal({
1369 IPython.dialog.modal({
1374 title : "Restart kernel or continue running?",
1370 title : "Restart kernel or continue running?",
1375 body : $("<p/>").html(
1371 body : $("<p/>").html(
1376 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1372 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1377 ),
1373 ),
1378 buttons : {
1374 buttons : {
1379 "Continue running" : {},
1375 "Continue running" : {},
1380 "Restart" : {
1376 "Restart" : {
1381 "class" : "btn-danger",
1377 "class" : "btn-danger",
1382 "click" : function() {
1378 "click" : function() {
1383 that.kernel.restart();
1379 that.kernel.restart();
1384 }
1380 }
1385 }
1381 }
1386 }
1382 }
1387 });
1383 });
1388 };
1384 };
1389
1385
1390 /**
1386 /**
1391 * Run the selected cell.
1387 * Run the selected cell.
1392 *
1388 *
1393 * Execute or render cell outputs.
1389 * Execute or render cell outputs.
1394 *
1390 *
1395 * @method execute_selected_cell
1391 * @method execute_selected_cell
1396 * @param {Object} options Customize post-execution behavior
1392 * @param {Object} options Customize post-execution behavior
1397 */
1393 */
1398 Notebook.prototype.execute_selected_cell = function (options) {
1394 Notebook.prototype.execute_selected_cell = function (options) {
1399 // add_new: should a new cell be added if we are at the end of the nb
1395 // add_new: should a new cell be added if we are at the end of the nb
1400 // terminal: execute in terminal mode, which stays in the current cell
1396 // terminal: execute in terminal mode, which stays in the current cell
1401 var default_options = {terminal: false, add_new: true};
1397 var default_options = {terminal: false, add_new: true};
1402 $.extend(default_options, options);
1398 $.extend(default_options, options);
1403 var that = this;
1399 var that = this;
1404 var cell = that.get_selected_cell();
1400 var cell = that.get_selected_cell();
1405 var cell_index = that.find_cell_index(cell);
1401 var cell_index = that.find_cell_index(cell);
1406 if (cell instanceof IPython.CodeCell) {
1402 if (cell instanceof IPython.CodeCell) {
1407 cell.execute();
1403 cell.execute();
1408 }
1404 }
1409 if (default_options.terminal) {
1405 if (default_options.terminal) {
1410 cell.select_all();
1406 cell.select_all();
1411 } else {
1407 } else {
1412 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1408 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1413 that.insert_cell_below('code');
1409 that.insert_cell_below('code');
1414 // If we are adding a new cell at the end, scroll down to show it.
1410 // If we are adding a new cell at the end, scroll down to show it.
1415 that.scroll_to_bottom();
1411 that.scroll_to_bottom();
1416 } else {
1412 } else {
1417 that.select(cell_index+1);
1413 that.select(cell_index+1);
1418 };
1414 };
1419 };
1415 };
1420 this.set_dirty(true);
1416 this.set_dirty(true);
1421 };
1417 };
1422
1418
1423 /**
1419 /**
1424 * Execute all cells below the selected cell.
1420 * Execute all cells below the selected cell.
1425 *
1421 *
1426 * @method execute_cells_below
1422 * @method execute_cells_below
1427 */
1423 */
1428 Notebook.prototype.execute_cells_below = function () {
1424 Notebook.prototype.execute_cells_below = function () {
1429 this.execute_cell_range(this.get_selected_index(), this.ncells());
1425 this.execute_cell_range(this.get_selected_index(), this.ncells());
1430 this.scroll_to_bottom();
1426 this.scroll_to_bottom();
1431 };
1427 };
1432
1428
1433 /**
1429 /**
1434 * Execute all cells above the selected cell.
1430 * Execute all cells above the selected cell.
1435 *
1431 *
1436 * @method execute_cells_above
1432 * @method execute_cells_above
1437 */
1433 */
1438 Notebook.prototype.execute_cells_above = function () {
1434 Notebook.prototype.execute_cells_above = function () {
1439 this.execute_cell_range(0, this.get_selected_index());
1435 this.execute_cell_range(0, this.get_selected_index());
1440 };
1436 };
1441
1437
1442 /**
1438 /**
1443 * Execute all cells.
1439 * Execute all cells.
1444 *
1440 *
1445 * @method execute_all_cells
1441 * @method execute_all_cells
1446 */
1442 */
1447 Notebook.prototype.execute_all_cells = function () {
1443 Notebook.prototype.execute_all_cells = function () {
1448 this.execute_cell_range(0, this.ncells());
1444 this.execute_cell_range(0, this.ncells());
1449 this.scroll_to_bottom();
1445 this.scroll_to_bottom();
1450 };
1446 };
1451
1447
1452 /**
1448 /**
1453 * Execute a contiguous range of cells.
1449 * Execute a contiguous range of cells.
1454 *
1450 *
1455 * @method execute_cell_range
1451 * @method execute_cell_range
1456 * @param {Number} start Index of the first cell to execute (inclusive)
1452 * @param {Number} start Index of the first cell to execute (inclusive)
1457 * @param {Number} end Index of the last cell to execute (exclusive)
1453 * @param {Number} end Index of the last cell to execute (exclusive)
1458 */
1454 */
1459 Notebook.prototype.execute_cell_range = function (start, end) {
1455 Notebook.prototype.execute_cell_range = function (start, end) {
1460 for (var i=start; i<end; i++) {
1456 for (var i=start; i<end; i++) {
1461 this.select(i);
1457 this.select(i);
1462 this.execute_selected_cell({add_new:false});
1458 this.execute_selected_cell({add_new:false});
1463 };
1459 };
1464 };
1460 };
1465
1461
1466 // Persistance and loading
1462 // Persistance and loading
1467
1463
1468 /**
1464 /**
1469 * Getter method for this notebook's ID.
1465 * Getter method for this notebook's ID.
1470 *
1466 *
1471 * @method get_notebook_id
1467 * @method get_notebook_id
1472 * @return {String} This notebook's ID
1468 * @return {String} This notebook's ID
1473 */
1469 */
1474 Notebook.prototype.get_notebook_id = function () {
1470 Notebook.prototype.get_notebook_id = function () {
1475 return this.notebook_id;
1471 return this.notebook_id;
1476 };
1472 };
1477
1473
1478 /**
1474 /**
1479 * Getter method for this notebook's name.
1475 * Getter method for this notebook's name.
1480 *
1476 *
1481 * @method get_notebook_name
1477 * @method get_notebook_name
1482 * @return {String} This notebook's name
1478 * @return {String} This notebook's name
1483 */
1479 */
1484 Notebook.prototype.get_notebook_name = function () {
1480 Notebook.prototype.get_notebook_name = function () {
1485 return this.notebook_name;
1481 return this.notebook_name;
1486 };
1482 };
1487
1483
1488 /**
1484 /**
1489 * Setter method for this notebook's name.
1485 * Setter method for this notebook's name.
1490 *
1486 *
1491 * @method set_notebook_name
1487 * @method set_notebook_name
1492 * @param {String} name A new name for this notebook
1488 * @param {String} name A new name for this notebook
1493 */
1489 */
1494 Notebook.prototype.set_notebook_name = function (name) {
1490 Notebook.prototype.set_notebook_name = function (name) {
1495 this.notebook_name = name;
1491 this.notebook_name = name;
1496 };
1492 };
1497
1493
1498 /**
1494 /**
1499 * Check that a notebook's name is valid.
1495 * Check that a notebook's name is valid.
1500 *
1496 *
1501 * @method test_notebook_name
1497 * @method test_notebook_name
1502 * @param {String} nbname A name for this notebook
1498 * @param {String} nbname A name for this notebook
1503 * @return {Boolean} True if the name is valid, false if invalid
1499 * @return {Boolean} True if the name is valid, false if invalid
1504 */
1500 */
1505 Notebook.prototype.test_notebook_name = function (nbname) {
1501 Notebook.prototype.test_notebook_name = function (nbname) {
1506 nbname = nbname || '';
1502 nbname = nbname || '';
1507 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1503 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1508 return true;
1504 return true;
1509 } else {
1505 } else {
1510 return false;
1506 return false;
1511 };
1507 };
1512 };
1508 };
1513
1509
1514 /**
1510 /**
1515 * Load a notebook from JSON (.ipynb).
1511 * Load a notebook from JSON (.ipynb).
1516 *
1512 *
1517 * This currently handles one worksheet: others are deleted.
1513 * This currently handles one worksheet: others are deleted.
1518 *
1514 *
1519 * @method fromJSON
1515 * @method fromJSON
1520 * @param {Object} data JSON representation of a notebook
1516 * @param {Object} data JSON representation of a notebook
1521 */
1517 */
1522 Notebook.prototype.fromJSON = function (data) {
1518 Notebook.prototype.fromJSON = function (data) {
1523 var ncells = this.ncells();
1519 var ncells = this.ncells();
1524 var i;
1520 var i;
1525 for (i=0; i<ncells; i++) {
1521 for (i=0; i<ncells; i++) {
1526 // Always delete cell 0 as they get renumbered as they are deleted.
1522 // Always delete cell 0 as they get renumbered as they are deleted.
1527 this.delete_cell(0);
1523 this.delete_cell(0);
1528 };
1524 };
1529 // Save the metadata and name.
1525 // Save the metadata and name.
1530 this.metadata = data.metadata;
1526 this.metadata = data.metadata;
1531 this.notebook_name = data.metadata.name;
1527 this.notebook_name = data.metadata.name;
1532 // Only handle 1 worksheet for now.
1528 // Only handle 1 worksheet for now.
1533 var worksheet = data.worksheets[0];
1529 var worksheet = data.worksheets[0];
1534 if (worksheet !== undefined) {
1530 if (worksheet !== undefined) {
1535 if (worksheet.metadata) {
1531 if (worksheet.metadata) {
1536 this.worksheet_metadata = worksheet.metadata;
1532 this.worksheet_metadata = worksheet.metadata;
1537 }
1533 }
1538 var new_cells = worksheet.cells;
1534 var new_cells = worksheet.cells;
1539 ncells = new_cells.length;
1535 ncells = new_cells.length;
1540 var cell_data = null;
1536 var cell_data = null;
1541 var new_cell = null;
1537 var new_cell = null;
1542 for (i=0; i<ncells; i++) {
1538 for (i=0; i<ncells; i++) {
1543 cell_data = new_cells[i];
1539 cell_data = new_cells[i];
1544 // VERSIONHACK: plaintext -> raw
1540 // VERSIONHACK: plaintext -> raw
1545 // handle never-released plaintext name for raw cells
1541 // handle never-released plaintext name for raw cells
1546 if (cell_data.cell_type === 'plaintext'){
1542 if (cell_data.cell_type === 'plaintext'){
1547 cell_data.cell_type = 'raw';
1543 cell_data.cell_type = 'raw';
1548 }
1544 }
1549
1545
1550 new_cell = this.insert_cell_below(cell_data.cell_type);
1546 new_cell = this.insert_cell_below(cell_data.cell_type);
1551 new_cell.fromJSON(cell_data);
1547 new_cell.fromJSON(cell_data);
1552 };
1548 };
1553 };
1549 };
1554 if (data.worksheets.length > 1) {
1550 if (data.worksheets.length > 1) {
1555 IPython.dialog.modal({
1551 IPython.dialog.modal({
1556 title : "Multiple worksheets",
1552 title : "Multiple worksheets",
1557 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1553 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1558 "but this version of IPython can only handle the first. " +
1554 "but this version of IPython can only handle the first. " +
1559 "If you save this notebook, worksheets after the first will be lost.",
1555 "If you save this notebook, worksheets after the first will be lost.",
1560 buttons : {
1556 buttons : {
1561 OK : {
1557 OK : {
1562 class : "btn-danger"
1558 class : "btn-danger"
1563 }
1559 }
1564 }
1560 }
1565 });
1561 });
1566 }
1562 }
1567 };
1563 };
1568
1564
1569 /**
1565 /**
1570 * Dump this notebook into a JSON-friendly object.
1566 * Dump this notebook into a JSON-friendly object.
1571 *
1567 *
1572 * @method toJSON
1568 * @method toJSON
1573 * @return {Object} A JSON-friendly representation of this notebook.
1569 * @return {Object} A JSON-friendly representation of this notebook.
1574 */
1570 */
1575 Notebook.prototype.toJSON = function () {
1571 Notebook.prototype.toJSON = function () {
1576 var cells = this.get_cells();
1572 var cells = this.get_cells();
1577 var ncells = cells.length;
1573 var ncells = cells.length;
1578 var cell_array = new Array(ncells);
1574 var cell_array = new Array(ncells);
1579 for (var i=0; i<ncells; i++) {
1575 for (var i=0; i<ncells; i++) {
1580 cell_array[i] = cells[i].toJSON();
1576 cell_array[i] = cells[i].toJSON();
1581 };
1577 };
1582 var data = {
1578 var data = {
1583 // Only handle 1 worksheet for now.
1579 // Only handle 1 worksheet for now.
1584 worksheets : [{
1580 worksheets : [{
1585 cells: cell_array,
1581 cells: cell_array,
1586 metadata: this.worksheet_metadata
1582 metadata: this.worksheet_metadata
1587 }],
1583 }],
1588 metadata : this.metadata
1584 metadata : this.metadata
1589 };
1585 };
1590 return data;
1586 return data;
1591 };
1587 };
1592
1588
1593 /**
1589 /**
1594 * Start an autosave timer, for periodically saving the notebook.
1590 * Start an autosave timer, for periodically saving the notebook.
1595 *
1591 *
1596 * @method set_autosave_interval
1592 * @method set_autosave_interval
1597 * @param {Integer} interval the autosave interval in milliseconds
1593 * @param {Integer} interval the autosave interval in milliseconds
1598 */
1594 */
1599 Notebook.prototype.set_autosave_interval = function (interval) {
1595 Notebook.prototype.set_autosave_interval = function (interval) {
1600 var that = this;
1596 var that = this;
1601 // clear previous interval, so we don't get simultaneous timers
1597 // clear previous interval, so we don't get simultaneous timers
1602 if (this.autosave_timer) {
1598 if (this.autosave_timer) {
1603 clearInterval(this.autosave_timer);
1599 clearInterval(this.autosave_timer);
1604 }
1600 }
1605
1601
1606 this.autosave_interval = this.minimum_autosave_interval = interval;
1602 this.autosave_interval = this.minimum_autosave_interval = interval;
1607 if (interval) {
1603 if (interval) {
1608 this.autosave_timer = setInterval(function() {
1604 this.autosave_timer = setInterval(function() {
1609 if (that.dirty) {
1605 if (that.dirty) {
1610 that.save_notebook();
1606 that.save_notebook();
1611 }
1607 }
1612 }, interval);
1608 }, interval);
1613 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1609 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1614 } else {
1610 } else {
1615 this.autosave_timer = null;
1611 this.autosave_timer = null;
1616 $([IPython.events]).trigger("autosave_disabled.Notebook");
1612 $([IPython.events]).trigger("autosave_disabled.Notebook");
1617 };
1613 };
1618 };
1614 };
1619
1615
1620 /**
1616 /**
1621 * Save this notebook on the server.
1617 * Save this notebook on the server.
1622 *
1618 *
1623 * @method save_notebook
1619 * @method save_notebook
1624 */
1620 */
1625 Notebook.prototype.save_notebook = function () {
1621 Notebook.prototype.save_notebook = function () {
1626 // We may want to move the name/id/nbformat logic inside toJSON?
1622 // We may want to move the name/id/nbformat logic inside toJSON?
1627 var data = this.toJSON();
1623 var data = this.toJSON();
1628 data.metadata.name = this.notebook_name;
1624 data.metadata.name = this.notebook_name;
1629 data.nbformat = this.nbformat;
1625 data.nbformat = this.nbformat;
1630 data.nbformat_minor = this.nbformat_minor;
1626 data.nbformat_minor = this.nbformat_minor;
1631
1627
1632 // time the ajax call for autosave tuning purposes.
1628 // time the ajax call for autosave tuning purposes.
1633 var start = new Date().getTime();
1629 var start = new Date().getTime();
1634
1630
1635 // We do the call with settings so we can set cache to false.
1631 // We do the call with settings so we can set cache to false.
1636 var settings = {
1632 var settings = {
1637 processData : false,
1633 processData : false,
1638 cache : false,
1634 cache : false,
1639 type : "PUT",
1635 type : "PUT",
1640 data : JSON.stringify(data),
1636 data : JSON.stringify(data),
1641 headers : {'Content-Type': 'application/json'},
1637 headers : {'Content-Type': 'application/json'},
1642 success : $.proxy(this.save_notebook_success, this, start),
1638 success : $.proxy(this.save_notebook_success, this, start),
1643 error : $.proxy(this.save_notebook_error, this)
1639 error : $.proxy(this.save_notebook_error, this)
1644 };
1640 };
1645 $([IPython.events]).trigger('notebook_saving.Notebook');
1641 $([IPython.events]).trigger('notebook_saving.Notebook');
1646 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1642 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1647 $.ajax(url, settings);
1643 $.ajax(url, settings);
1648 };
1644 };
1649
1645
1650 /**
1646 /**
1651 * Success callback for saving a notebook.
1647 * Success callback for saving a notebook.
1652 *
1648 *
1653 * @method save_notebook_success
1649 * @method save_notebook_success
1654 * @param {Integer} start the time when the save request started
1650 * @param {Integer} start the time when the save request started
1655 * @param {Object} data JSON representation of a notebook
1651 * @param {Object} data JSON representation of a notebook
1656 * @param {String} status Description of response status
1652 * @param {String} status Description of response status
1657 * @param {jqXHR} xhr jQuery Ajax object
1653 * @param {jqXHR} xhr jQuery Ajax object
1658 */
1654 */
1659 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1655 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1660 this.set_dirty(false);
1656 this.set_dirty(false);
1661 $([IPython.events]).trigger('notebook_saved.Notebook');
1657 $([IPython.events]).trigger('notebook_saved.Notebook');
1662 this._update_autosave_interval(start);
1658 this._update_autosave_interval(start);
1663 if (this._checkpoint_after_save) {
1659 if (this._checkpoint_after_save) {
1664 this.create_checkpoint();
1660 this.create_checkpoint();
1665 this._checkpoint_after_save = false;
1661 this._checkpoint_after_save = false;
1666 };
1662 };
1667 };
1663 };
1668
1664
1669 /**
1665 /**
1670 * update the autosave interval based on how long the last save took
1666 * update the autosave interval based on how long the last save took
1671 *
1667 *
1672 * @method _update_autosave_interval
1668 * @method _update_autosave_interval
1673 * @param {Integer} timestamp when the save request started
1669 * @param {Integer} timestamp when the save request started
1674 */
1670 */
1675 Notebook.prototype._update_autosave_interval = function (start) {
1671 Notebook.prototype._update_autosave_interval = function (start) {
1676 var duration = (new Date().getTime() - start);
1672 var duration = (new Date().getTime() - start);
1677 if (this.autosave_interval) {
1673 if (this.autosave_interval) {
1678 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1674 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1679 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1675 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1680 // round to 10 seconds, otherwise we will be setting a new interval too often
1676 // round to 10 seconds, otherwise we will be setting a new interval too often
1681 interval = 10000 * Math.round(interval / 10000);
1677 interval = 10000 * Math.round(interval / 10000);
1682 // set new interval, if it's changed
1678 // set new interval, if it's changed
1683 if (interval != this.autosave_interval) {
1679 if (interval != this.autosave_interval) {
1684 this.set_autosave_interval(interval);
1680 this.set_autosave_interval(interval);
1685 }
1681 }
1686 }
1682 }
1687 };
1683 };
1688
1684
1689 /**
1685 /**
1690 * Failure callback for saving a notebook.
1686 * Failure callback for saving a notebook.
1691 *
1687 *
1692 * @method save_notebook_error
1688 * @method save_notebook_error
1693 * @param {jqXHR} xhr jQuery Ajax object
1689 * @param {jqXHR} xhr jQuery Ajax object
1694 * @param {String} status Description of response status
1690 * @param {String} status Description of response status
1695 * @param {String} error_msg HTTP error message
1691 * @param {String} error_msg HTTP error message
1696 */
1692 */
1697 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1693 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1698 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1694 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1699 };
1695 };
1700
1696
1701 /**
1697 /**
1702 * Request a notebook's data from the server.
1698 * Request a notebook's data from the server.
1703 *
1699 *
1704 * @method load_notebook
1700 * @method load_notebook
1705 * @param {String} notebook_id A notebook to load
1701 * @param {String} notebook_id A notebook to load
1706 */
1702 */
1707 Notebook.prototype.load_notebook = function (notebook_id) {
1703 Notebook.prototype.load_notebook = function (notebook_id) {
1708 var that = this;
1704 var that = this;
1709 this.notebook_id = notebook_id;
1705 this.notebook_id = notebook_id;
1710 // We do the call with settings so we can set cache to false.
1706 // We do the call with settings so we can set cache to false.
1711 var settings = {
1707 var settings = {
1712 processData : false,
1708 processData : false,
1713 cache : false,
1709 cache : false,
1714 type : "GET",
1710 type : "GET",
1715 dataType : "json",
1711 dataType : "json",
1716 success : $.proxy(this.load_notebook_success,this),
1712 success : $.proxy(this.load_notebook_success,this),
1717 error : $.proxy(this.load_notebook_error,this),
1713 error : $.proxy(this.load_notebook_error,this),
1718 };
1714 };
1719 $([IPython.events]).trigger('notebook_loading.Notebook');
1715 $([IPython.events]).trigger('notebook_loading.Notebook');
1720 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1716 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1721 $.ajax(url, settings);
1717 $.ajax(url, settings);
1722 };
1718 };
1723
1719
1724 /**
1720 /**
1725 * Success callback for loading a notebook from the server.
1721 * Success callback for loading a notebook from the server.
1726 *
1722 *
1727 * Load notebook data from the JSON response.
1723 * Load notebook data from the JSON response.
1728 *
1724 *
1729 * @method load_notebook_success
1725 * @method load_notebook_success
1730 * @param {Object} data JSON representation of a notebook
1726 * @param {Object} data JSON representation of a notebook
1731 * @param {String} status Description of response status
1727 * @param {String} status Description of response status
1732 * @param {jqXHR} xhr jQuery Ajax object
1728 * @param {jqXHR} xhr jQuery Ajax object
1733 */
1729 */
1734 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1730 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1735 this.fromJSON(data);
1731 this.fromJSON(data);
1736 if (this.ncells() === 0) {
1732 if (this.ncells() === 0) {
1737 this.insert_cell_below('code');
1733 this.insert_cell_below('code');
1738 };
1734 };
1739 this.set_dirty(false);
1735 this.set_dirty(false);
1740 this.select(0);
1736 this.select(0);
1741 this.scroll_to_top();
1737 this.scroll_to_top();
1742 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1738 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1743 var msg = "This notebook has been converted from an older " +
1739 var msg = "This notebook has been converted from an older " +
1744 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1740 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1745 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1741 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1746 "newer notebook format will be used and older versions of IPython " +
1742 "newer notebook format will be used and older versions of IPython " +
1747 "may not be able to read it. To keep the older version, close the " +
1743 "may not be able to read it. To keep the older version, close the " +
1748 "notebook without saving it.";
1744 "notebook without saving it.";
1749 IPython.dialog.modal({
1745 IPython.dialog.modal({
1750 title : "Notebook converted",
1746 title : "Notebook converted",
1751 body : msg,
1747 body : msg,
1752 buttons : {
1748 buttons : {
1753 OK : {
1749 OK : {
1754 class : "btn-primary"
1750 class : "btn-primary"
1755 }
1751 }
1756 }
1752 }
1757 });
1753 });
1758 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1754 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1759 var that = this;
1755 var that = this;
1760 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1756 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1761 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1757 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1762 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1758 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1763 this_vs + ". You can still work with this notebook, but some features " +
1759 this_vs + ". You can still work with this notebook, but some features " +
1764 "introduced in later notebook versions may not be available."
1760 "introduced in later notebook versions may not be available."
1765
1761
1766 IPython.dialog.modal({
1762 IPython.dialog.modal({
1767 title : "Newer Notebook",
1763 title : "Newer Notebook",
1768 body : msg,
1764 body : msg,
1769 buttons : {
1765 buttons : {
1770 OK : {
1766 OK : {
1771 class : "btn-danger"
1767 class : "btn-danger"
1772 }
1768 }
1773 }
1769 }
1774 });
1770 });
1775
1771
1776 }
1772 }
1777
1773
1778 // Create the kernel after the notebook is completely loaded to prevent
1774 // Create the kernel after the notebook is completely loaded to prevent
1779 // code execution upon loading, which is a security risk.
1775 // code execution upon loading, which is a security risk.
1780 if (! this.read_only) {
1776 this.start_kernel();
1781 this.start_kernel();
1777 // load our checkpoint list
1782 // load our checkpoint list
1778 IPython.notebook.list_checkpoints();
1783 IPython.notebook.list_checkpoints();
1779
1784 }
1785 $([IPython.events]).trigger('notebook_loaded.Notebook');
1780 $([IPython.events]).trigger('notebook_loaded.Notebook');
1786 };
1781 };
1787
1782
1788 /**
1783 /**
1789 * Failure callback for loading a notebook from the server.
1784 * Failure callback for loading a notebook from the server.
1790 *
1785 *
1791 * @method load_notebook_error
1786 * @method load_notebook_error
1792 * @param {jqXHR} xhr jQuery Ajax object
1787 * @param {jqXHR} xhr jQuery Ajax object
1793 * @param {String} textStatus Description of response status
1788 * @param {String} textStatus Description of response status
1794 * @param {String} errorThrow HTTP error message
1789 * @param {String} errorThrow HTTP error message
1795 */
1790 */
1796 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1791 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1797 if (xhr.status === 500) {
1792 if (xhr.status === 500) {
1798 var msg = "An error occurred while loading this notebook. Most likely " +
1793 var msg = "An error occurred while loading this notebook. Most likely " +
1799 "this notebook is in a newer format than is supported by this " +
1794 "this notebook is in a newer format than is supported by this " +
1800 "version of IPython. This version can load notebook formats " +
1795 "version of IPython. This version can load notebook formats " +
1801 "v"+this.nbformat+" or earlier.";
1796 "v"+this.nbformat+" or earlier.";
1802
1797
1803 IPython.dialog.modal({
1798 IPython.dialog.modal({
1804 title: "Error loading notebook",
1799 title: "Error loading notebook",
1805 body : msg,
1800 body : msg,
1806 buttons : {
1801 buttons : {
1807 "OK": {}
1802 "OK": {}
1808 }
1803 }
1809 });
1804 });
1810 }
1805 }
1811 }
1806 }
1812
1807
1813 /********************* checkpoint-related *********************/
1808 /********************* checkpoint-related *********************/
1814
1809
1815 /**
1810 /**
1816 * Save the notebook then immediately create a checkpoint.
1811 * Save the notebook then immediately create a checkpoint.
1817 *
1812 *
1818 * @method save_checkpoint
1813 * @method save_checkpoint
1819 */
1814 */
1820 Notebook.prototype.save_checkpoint = function () {
1815 Notebook.prototype.save_checkpoint = function () {
1821 this._checkpoint_after_save = true;
1816 this._checkpoint_after_save = true;
1822 this.save_notebook();
1817 this.save_notebook();
1823 };
1818 };
1824
1819
1825 /**
1820 /**
1826 * List checkpoints for this notebook.
1821 * List checkpoints for this notebook.
1827 *
1822 *
1828 * @method list_checkpoint
1823 * @method list_checkpoint
1829 */
1824 */
1830 Notebook.prototype.list_checkpoints = function () {
1825 Notebook.prototype.list_checkpoints = function () {
1831 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1826 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1832 $.get(url).done(
1827 $.get(url).done(
1833 $.proxy(this.list_checkpoints_success, this)
1828 $.proxy(this.list_checkpoints_success, this)
1834 ).fail(
1829 ).fail(
1835 $.proxy(this.list_checkpoints_error, this)
1830 $.proxy(this.list_checkpoints_error, this)
1836 );
1831 );
1837 };
1832 };
1838
1833
1839 /**
1834 /**
1840 * Success callback for listing checkpoints.
1835 * Success callback for listing checkpoints.
1841 *
1836 *
1842 * @method list_checkpoint_success
1837 * @method list_checkpoint_success
1843 * @param {Object} data JSON representation of a checkpoint
1838 * @param {Object} data JSON representation of a checkpoint
1844 * @param {String} status Description of response status
1839 * @param {String} status Description of response status
1845 * @param {jqXHR} xhr jQuery Ajax object
1840 * @param {jqXHR} xhr jQuery Ajax object
1846 */
1841 */
1847 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1842 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1848 var data = $.parseJSON(data);
1843 var data = $.parseJSON(data);
1849 if (data.length) {
1844 if (data.length) {
1850 this.last_checkpoint = data[0];
1845 this.last_checkpoint = data[0];
1851 } else {
1846 } else {
1852 this.last_checkpoint = null;
1847 this.last_checkpoint = null;
1853 }
1848 }
1854 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1849 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1855 };
1850 };
1856
1851
1857 /**
1852 /**
1858 * Failure callback for listing a checkpoint.
1853 * Failure callback for listing a checkpoint.
1859 *
1854 *
1860 * @method list_checkpoint_error
1855 * @method list_checkpoint_error
1861 * @param {jqXHR} xhr jQuery Ajax object
1856 * @param {jqXHR} xhr jQuery Ajax object
1862 * @param {String} status Description of response status
1857 * @param {String} status Description of response status
1863 * @param {String} error_msg HTTP error message
1858 * @param {String} error_msg HTTP error message
1864 */
1859 */
1865 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1860 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1866 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1861 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1867 };
1862 };
1868
1863
1869 /**
1864 /**
1870 * Create a checkpoint of this notebook on the server from the most recent save.
1865 * Create a checkpoint of this notebook on the server from the most recent save.
1871 *
1866 *
1872 * @method create_checkpoint
1867 * @method create_checkpoint
1873 */
1868 */
1874 Notebook.prototype.create_checkpoint = function () {
1869 Notebook.prototype.create_checkpoint = function () {
1875 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1870 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1876 $.post(url).done(
1871 $.post(url).done(
1877 $.proxy(this.create_checkpoint_success, this)
1872 $.proxy(this.create_checkpoint_success, this)
1878 ).fail(
1873 ).fail(
1879 $.proxy(this.create_checkpoint_error, this)
1874 $.proxy(this.create_checkpoint_error, this)
1880 );
1875 );
1881 };
1876 };
1882
1877
1883 /**
1878 /**
1884 * Success callback for creating a checkpoint.
1879 * Success callback for creating a checkpoint.
1885 *
1880 *
1886 * @method create_checkpoint_success
1881 * @method create_checkpoint_success
1887 * @param {Object} data JSON representation of a checkpoint
1882 * @param {Object} data JSON representation of a checkpoint
1888 * @param {String} status Description of response status
1883 * @param {String} status Description of response status
1889 * @param {jqXHR} xhr jQuery Ajax object
1884 * @param {jqXHR} xhr jQuery Ajax object
1890 */
1885 */
1891 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1886 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1892 var data = $.parseJSON(data);
1887 var data = $.parseJSON(data);
1893 this.last_checkpoint = data;
1888 this.last_checkpoint = data;
1894 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1889 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1895 };
1890 };
1896
1891
1897 /**
1892 /**
1898 * Failure callback for creating a checkpoint.
1893 * Failure callback for creating a checkpoint.
1899 *
1894 *
1900 * @method create_checkpoint_error
1895 * @method create_checkpoint_error
1901 * @param {jqXHR} xhr jQuery Ajax object
1896 * @param {jqXHR} xhr jQuery Ajax object
1902 * @param {String} status Description of response status
1897 * @param {String} status Description of response status
1903 * @param {String} error_msg HTTP error message
1898 * @param {String} error_msg HTTP error message
1904 */
1899 */
1905 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1900 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1906 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1901 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1907 };
1902 };
1908
1903
1909 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1904 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1910 var that = this;
1905 var that = this;
1911 var checkpoint = checkpoint || this.last_checkpoint;
1906 var checkpoint = checkpoint || this.last_checkpoint;
1912 if ( ! checkpoint ) {
1907 if ( ! checkpoint ) {
1913 console.log("restore dialog, but no checkpoint to restore to!");
1908 console.log("restore dialog, but no checkpoint to restore to!");
1914 return;
1909 return;
1915 }
1910 }
1916 var body = $('<div/>').append(
1911 var body = $('<div/>').append(
1917 $('<p/>').addClass("p-space").text(
1912 $('<p/>').addClass("p-space").text(
1918 "Are you sure you want to revert the notebook to " +
1913 "Are you sure you want to revert the notebook to " +
1919 "the latest checkpoint?"
1914 "the latest checkpoint?"
1920 ).append(
1915 ).append(
1921 $("<strong/>").text(
1916 $("<strong/>").text(
1922 " This cannot be undone."
1917 " This cannot be undone."
1923 )
1918 )
1924 )
1919 )
1925 ).append(
1920 ).append(
1926 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1921 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1927 ).append(
1922 ).append(
1928 $('<p/>').addClass("p-space").text(
1923 $('<p/>').addClass("p-space").text(
1929 Date(checkpoint.last_modified)
1924 Date(checkpoint.last_modified)
1930 ).css("text-align", "center")
1925 ).css("text-align", "center")
1931 );
1926 );
1932
1927
1933 IPython.dialog.modal({
1928 IPython.dialog.modal({
1934 title : "Revert notebook to checkpoint",
1929 title : "Revert notebook to checkpoint",
1935 body : body,
1930 body : body,
1936 buttons : {
1931 buttons : {
1937 Revert : {
1932 Revert : {
1938 class : "btn-danger",
1933 class : "btn-danger",
1939 click : function () {
1934 click : function () {
1940 that.restore_checkpoint(checkpoint.checkpoint_id);
1935 that.restore_checkpoint(checkpoint.checkpoint_id);
1941 }
1936 }
1942 },
1937 },
1943 Cancel : {}
1938 Cancel : {}
1944 }
1939 }
1945 });
1940 });
1946 }
1941 }
1947
1942
1948 /**
1943 /**
1949 * Restore the notebook to a checkpoint state.
1944 * Restore the notebook to a checkpoint state.
1950 *
1945 *
1951 * @method restore_checkpoint
1946 * @method restore_checkpoint
1952 * @param {String} checkpoint ID
1947 * @param {String} checkpoint ID
1953 */
1948 */
1954 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1949 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1955 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1950 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1956 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1951 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1957 $.post(url).done(
1952 $.post(url).done(
1958 $.proxy(this.restore_checkpoint_success, this)
1953 $.proxy(this.restore_checkpoint_success, this)
1959 ).fail(
1954 ).fail(
1960 $.proxy(this.restore_checkpoint_error, this)
1955 $.proxy(this.restore_checkpoint_error, this)
1961 );
1956 );
1962 };
1957 };
1963
1958
1964 /**
1959 /**
1965 * Success callback for restoring a notebook to a checkpoint.
1960 * Success callback for restoring a notebook to a checkpoint.
1966 *
1961 *
1967 * @method restore_checkpoint_success
1962 * @method restore_checkpoint_success
1968 * @param {Object} data (ignored, should be empty)
1963 * @param {Object} data (ignored, should be empty)
1969 * @param {String} status Description of response status
1964 * @param {String} status Description of response status
1970 * @param {jqXHR} xhr jQuery Ajax object
1965 * @param {jqXHR} xhr jQuery Ajax object
1971 */
1966 */
1972 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
1967 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
1973 $([IPython.events]).trigger('checkpoint_restored.Notebook');
1968 $([IPython.events]).trigger('checkpoint_restored.Notebook');
1974 this.load_notebook(this.notebook_id);
1969 this.load_notebook(this.notebook_id);
1975 };
1970 };
1976
1971
1977 /**
1972 /**
1978 * Failure callback for restoring a notebook to a checkpoint.
1973 * Failure callback for restoring a notebook to a checkpoint.
1979 *
1974 *
1980 * @method restore_checkpoint_error
1975 * @method restore_checkpoint_error
1981 * @param {jqXHR} xhr jQuery Ajax object
1976 * @param {jqXHR} xhr jQuery Ajax object
1982 * @param {String} status Description of response status
1977 * @param {String} status Description of response status
1983 * @param {String} error_msg HTTP error message
1978 * @param {String} error_msg HTTP error message
1984 */
1979 */
1985 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
1980 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
1986 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
1981 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
1987 };
1982 };
1988
1983
1989 /**
1984 /**
1990 * Delete a notebook checkpoint.
1985 * Delete a notebook checkpoint.
1991 *
1986 *
1992 * @method delete_checkpoint
1987 * @method delete_checkpoint
1993 * @param {String} checkpoint ID
1988 * @param {String} checkpoint ID
1994 */
1989 */
1995 Notebook.prototype.delete_checkpoint = function (checkpoint) {
1990 Notebook.prototype.delete_checkpoint = function (checkpoint) {
1996 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
1991 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
1997 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1992 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1998 $.ajax(url, {
1993 $.ajax(url, {
1999 type: 'DELETE',
1994 type: 'DELETE',
2000 success: $.proxy(this.delete_checkpoint_success, this),
1995 success: $.proxy(this.delete_checkpoint_success, this),
2001 error: $.proxy(this.delete_notebook_error,this)
1996 error: $.proxy(this.delete_notebook_error,this)
2002 });
1997 });
2003 };
1998 };
2004
1999
2005 /**
2000 /**
2006 * Success callback for deleting a notebook checkpoint
2001 * Success callback for deleting a notebook checkpoint
2007 *
2002 *
2008 * @method delete_checkpoint_success
2003 * @method delete_checkpoint_success
2009 * @param {Object} data (ignored, should be empty)
2004 * @param {Object} data (ignored, should be empty)
2010 * @param {String} status Description of response status
2005 * @param {String} status Description of response status
2011 * @param {jqXHR} xhr jQuery Ajax object
2006 * @param {jqXHR} xhr jQuery Ajax object
2012 */
2007 */
2013 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2008 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2014 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2009 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2015 this.load_notebook(this.notebook_id);
2010 this.load_notebook(this.notebook_id);
2016 };
2011 };
2017
2012
2018 /**
2013 /**
2019 * Failure callback for deleting a notebook checkpoint.
2014 * Failure callback for deleting a notebook checkpoint.
2020 *
2015 *
2021 * @method delete_checkpoint_error
2016 * @method delete_checkpoint_error
2022 * @param {jqXHR} xhr jQuery Ajax object
2017 * @param {jqXHR} xhr jQuery Ajax object
2023 * @param {String} status Description of response status
2018 * @param {String} status Description of response status
2024 * @param {String} error_msg HTTP error message
2019 * @param {String} error_msg HTTP error message
2025 */
2020 */
2026 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2021 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2027 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2022 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2028 };
2023 };
2029
2024
2030
2025
2031 IPython.Notebook = Notebook;
2026 IPython.Notebook = Notebook;
2032
2027
2033
2028
2034 return IPython;
2029 return IPython;
2035
2030
2036 }(IPython));
2031 }(IPython));
2037
2032
@@ -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