##// END OF EJS Templates
Merge master
Gordon Ball -
r17708:905e751b merge
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

This diff has been collapsed as it changes many lines, (531 lines changed) Show them Hide them
@@ -0,0 +1,531 b''
1 """A contents manager that uses the local file system for storage."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 import base64
7 import io
8 import os
9 import glob
10 import shutil
11
12 from tornado import web
13
14 from .manager import ContentsManager
15 from IPython.nbformat import current
16 from IPython.utils.path import ensure_dir_exists
17 from IPython.utils.traitlets import Unicode, Bool, TraitError
18 from IPython.utils.py3compat import getcwd
19 from IPython.utils import tz
20 from IPython.html.utils import is_hidden, to_os_path, url_path_join
21
22
23 class FileContentsManager(ContentsManager):
24
25 root_dir = Unicode(getcwd(), config=True)
26
27 save_script = Bool(False, config=True, help='DEPRECATED, IGNORED')
28 def _save_script_changed(self):
29 self.log.warn("""
30 Automatically saving notebooks as scripts has been removed.
31 Use `ipython nbconvert --to python [notebook]` instead.
32 """)
33
34 def _root_dir_changed(self, name, old, new):
35 """Do a bit of validation of the root_dir."""
36 if not os.path.isabs(new):
37 # If we receive a non-absolute path, make it absolute.
38 self.root_dir = os.path.abspath(new)
39 return
40 if not os.path.isdir(new):
41 raise TraitError("%r is not a directory" % new)
42
43 checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
44 help="""The directory name in which to keep file checkpoints
45
46 This is a path relative to the file's own directory.
47
48 By default, it is .ipynb_checkpoints
49 """
50 )
51
52 def _copy(self, src, dest):
53 """copy src to dest
54
55 like shutil.copy2, but log errors in copystat
56 """
57 shutil.copyfile(src, dest)
58 try:
59 shutil.copystat(src, dest)
60 except OSError as e:
61 self.log.debug("copystat on %s failed", dest, exc_info=True)
62
63 def _get_os_path(self, name=None, path=''):
64 """Given a filename and API path, return its file system
65 path.
66
67 Parameters
68 ----------
69 name : string
70 A filename
71 path : string
72 The relative API path to the named file.
73
74 Returns
75 -------
76 path : string
77 API path to be evaluated relative to root_dir.
78 """
79 if name is not None:
80 path = url_path_join(path, name)
81 return to_os_path(path, self.root_dir)
82
83 def path_exists(self, path):
84 """Does the API-style path refer to an extant directory?
85
86 API-style wrapper for os.path.isdir
87
88 Parameters
89 ----------
90 path : string
91 The path to check. This is an API path (`/` separated,
92 relative to root_dir).
93
94 Returns
95 -------
96 exists : bool
97 Whether the path is indeed a directory.
98 """
99 path = path.strip('/')
100 os_path = self._get_os_path(path=path)
101 return os.path.isdir(os_path)
102
103 def is_hidden(self, path):
104 """Does the API style path correspond to a hidden directory or file?
105
106 Parameters
107 ----------
108 path : string
109 The path to check. This is an API path (`/` separated,
110 relative to root_dir).
111
112 Returns
113 -------
114 exists : bool
115 Whether the path is hidden.
116
117 """
118 path = path.strip('/')
119 os_path = self._get_os_path(path=path)
120 return is_hidden(os_path, self.root_dir)
121
122 def file_exists(self, name, path=''):
123 """Returns True if the file exists, else returns False.
124
125 API-style wrapper for os.path.isfile
126
127 Parameters
128 ----------
129 name : string
130 The name of the file you are checking.
131 path : string
132 The relative path to the file's directory (with '/' as separator)
133
134 Returns
135 -------
136 exists : bool
137 Whether the file exists.
138 """
139 path = path.strip('/')
140 nbpath = self._get_os_path(name, path=path)
141 return os.path.isfile(nbpath)
142
143 def exists(self, name=None, path=''):
144 """Returns True if the path [and name] exists, else returns False.
145
146 API-style wrapper for os.path.exists
147
148 Parameters
149 ----------
150 name : string
151 The name of the file you are checking.
152 path : string
153 The relative path to the file's directory (with '/' as separator)
154
155 Returns
156 -------
157 exists : bool
158 Whether the target exists.
159 """
160 path = path.strip('/')
161 os_path = self._get_os_path(name, path=path)
162 return os.path.exists(os_path)
163
164 def _base_model(self, name, path=''):
165 """Build the common base of a contents model"""
166 os_path = self._get_os_path(name, path)
167 info = os.stat(os_path)
168 last_modified = tz.utcfromtimestamp(info.st_mtime)
169 created = tz.utcfromtimestamp(info.st_ctime)
170 # Create the base model.
171 model = {}
172 model['name'] = name
173 model['path'] = path
174 model['last_modified'] = last_modified
175 model['created'] = created
176 model['content'] = None
177 model['format'] = None
178 return model
179
180 def _dir_model(self, name, path='', content=True):
181 """Build a model for a directory
182
183 if content is requested, will include a listing of the directory
184 """
185 os_path = self._get_os_path(name, path)
186
187 four_o_four = u'directory does not exist: %r' % os_path
188
189 if not os.path.isdir(os_path):
190 raise web.HTTPError(404, four_o_four)
191 elif is_hidden(os_path, self.root_dir):
192 self.log.info("Refusing to serve hidden directory %r, via 404 Error",
193 os_path
194 )
195 raise web.HTTPError(404, four_o_four)
196
197 if name is None:
198 if '/' in path:
199 path, name = path.rsplit('/', 1)
200 else:
201 name = ''
202 model = self._base_model(name, path)
203 model['type'] = 'directory'
204 dir_path = u'{}/{}'.format(path, name)
205 if content:
206 model['content'] = contents = []
207 for os_path in glob.glob(self._get_os_path('*', dir_path)):
208 name = os.path.basename(os_path)
209 if self.should_list(name) and not is_hidden(os_path, self.root_dir):
210 contents.append(self.get_model(name=name, path=dir_path, content=False))
211
212 model['format'] = 'json'
213
214 return model
215
216 def _file_model(self, name, path='', content=True):
217 """Build a model for a file
218
219 if content is requested, include the file contents.
220 UTF-8 text files will be unicode, binary files will be base64-encoded.
221 """
222 model = self._base_model(name, path)
223 model['type'] = 'file'
224 if content:
225 os_path = self._get_os_path(name, path)
226 with io.open(os_path, 'rb') as f:
227 bcontent = f.read()
228 try:
229 model['content'] = bcontent.decode('utf8')
230 except UnicodeError as e:
231 model['content'] = base64.encodestring(bcontent).decode('ascii')
232 model['format'] = 'base64'
233 else:
234 model['format'] = 'text'
235 return model
236
237
238 def _notebook_model(self, name, path='', content=True):
239 """Build a notebook model
240
241 if content is requested, the notebook content will be populated
242 as a JSON structure (not double-serialized)
243 """
244 model = self._base_model(name, path)
245 model['type'] = 'notebook'
246 if content:
247 os_path = self._get_os_path(name, path)
248 with io.open(os_path, 'r', encoding='utf-8') as f:
249 try:
250 nb = current.read(f, u'json')
251 except Exception as e:
252 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
253 self.mark_trusted_cells(nb, name, path)
254 model['content'] = nb
255 model['format'] = 'json'
256 return model
257
258 def get_model(self, name, path='', content=True):
259 """ Takes a path and name for an entity and returns its model
260
261 Parameters
262 ----------
263 name : str
264 the name of the target
265 path : str
266 the API path that describes the relative path for the target
267
268 Returns
269 -------
270 model : dict
271 the contents model. If content=True, returns the contents
272 of the file or directory as well.
273 """
274 path = path.strip('/')
275
276 if not self.exists(name=name, path=path):
277 raise web.HTTPError(404, u'No such file or directory: %s/%s' % (path, name))
278
279 os_path = self._get_os_path(name, path)
280 if os.path.isdir(os_path):
281 model = self._dir_model(name, path, content)
282 elif name.endswith('.ipynb'):
283 model = self._notebook_model(name, path, content)
284 else:
285 model = self._file_model(name, path, content)
286 return model
287
288 def _save_notebook(self, os_path, model, name='', path=''):
289 """save a notebook file"""
290 # Save the notebook file
291 nb = current.to_notebook_json(model['content'])
292
293 self.check_and_sign(nb, name, path)
294
295 if 'name' in nb['metadata']:
296 nb['metadata']['name'] = u''
297
298 with io.open(os_path, 'w', encoding='utf-8') as f:
299 current.write(nb, f, u'json')
300
301 def _save_file(self, os_path, model, name='', path=''):
302 """save a non-notebook file"""
303 fmt = model.get('format', None)
304 if fmt not in {'text', 'base64'}:
305 raise web.HTTPError(400, "Must specify format of file contents as 'text' or 'base64'")
306 try:
307 content = model['content']
308 if fmt == 'text':
309 bcontent = content.encode('utf8')
310 else:
311 b64_bytes = content.encode('ascii')
312 bcontent = base64.decodestring(b64_bytes)
313 except Exception as e:
314 raise web.HTTPError(400, u'Encoding error saving %s: %s' % (os_path, e))
315 with io.open(os_path, 'wb') as f:
316 f.write(bcontent)
317
318 def _save_directory(self, os_path, model, name='', path=''):
319 """create a directory"""
320 if is_hidden(os_path, self.root_dir):
321 raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path)
322 if not os.path.exists(os_path):
323 os.mkdir(os_path)
324 elif not os.path.isdir(os_path):
325 raise web.HTTPError(400, u'Not a directory: %s' % (os_path))
326 else:
327 self.log.debug("Directory %r already exists", os_path)
328
329 def save(self, model, name='', path=''):
330 """Save the file model and return the model with no content."""
331 path = path.strip('/')
332
333 if 'type' not in model:
334 raise web.HTTPError(400, u'No file type provided')
335 if 'content' not in model and model['type'] != 'directory':
336 raise web.HTTPError(400, u'No file content provided')
337
338 # One checkpoint should always exist
339 if self.file_exists(name, path) and not self.list_checkpoints(name, path):
340 self.create_checkpoint(name, path)
341
342 new_path = model.get('path', path).strip('/')
343 new_name = model.get('name', name)
344
345 if path != new_path or name != new_name:
346 self.rename(name, path, new_name, new_path)
347
348 os_path = self._get_os_path(new_name, new_path)
349 self.log.debug("Saving %s", os_path)
350 try:
351 if model['type'] == 'notebook':
352 self._save_notebook(os_path, model, new_name, new_path)
353 elif model['type'] == 'file':
354 self._save_file(os_path, model, new_name, new_path)
355 elif model['type'] == 'directory':
356 self._save_directory(os_path, model, new_name, new_path)
357 else:
358 raise web.HTTPError(400, "Unhandled contents type: %s" % model['type'])
359 except web.HTTPError:
360 raise
361 except Exception as e:
362 raise web.HTTPError(400, u'Unexpected error while saving file: %s %s' % (os_path, e))
363
364 model = self.get_model(new_name, new_path, content=False)
365 return model
366
367 def update(self, model, name, path=''):
368 """Update the file's path and/or name
369
370 For use in PATCH requests, to enable renaming a file without
371 re-uploading its contents. Only used for renaming at the moment.
372 """
373 path = path.strip('/')
374 new_name = model.get('name', name)
375 new_path = model.get('path', path).strip('/')
376 if path != new_path or name != new_name:
377 self.rename(name, path, new_name, new_path)
378 model = self.get_model(new_name, new_path, content=False)
379 return model
380
381 def delete(self, name, path=''):
382 """Delete file by name and path."""
383 path = path.strip('/')
384 os_path = self._get_os_path(name, path)
385 rm = os.unlink
386 if os.path.isdir(os_path):
387 listing = os.listdir(os_path)
388 # don't delete non-empty directories (checkpoints dir doesn't count)
389 if listing and listing != [self.checkpoint_dir]:
390 raise web.HTTPError(400, u'Directory %s not empty' % os_path)
391 elif not os.path.isfile(os_path):
392 raise web.HTTPError(404, u'File does not exist: %s' % os_path)
393
394 # clear checkpoints
395 for checkpoint in self.list_checkpoints(name, path):
396 checkpoint_id = checkpoint['id']
397 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
398 if os.path.isfile(cp_path):
399 self.log.debug("Unlinking checkpoint %s", cp_path)
400 os.unlink(cp_path)
401
402 if os.path.isdir(os_path):
403 self.log.debug("Removing directory %s", os_path)
404 shutil.rmtree(os_path)
405 else:
406 self.log.debug("Unlinking file %s", os_path)
407 rm(os_path)
408
409 def rename(self, old_name, old_path, new_name, new_path):
410 """Rename a file."""
411 old_path = old_path.strip('/')
412 new_path = new_path.strip('/')
413 if new_name == old_name and new_path == old_path:
414 return
415
416 new_os_path = self._get_os_path(new_name, new_path)
417 old_os_path = self._get_os_path(old_name, old_path)
418
419 # Should we proceed with the move?
420 if os.path.isfile(new_os_path):
421 raise web.HTTPError(409, u'File with name already exists: %s' % new_os_path)
422
423 # Move the file
424 try:
425 shutil.move(old_os_path, new_os_path)
426 except Exception as e:
427 raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_os_path, e))
428
429 # Move the checkpoints
430 old_checkpoints = self.list_checkpoints(old_name, old_path)
431 for cp in old_checkpoints:
432 checkpoint_id = cp['id']
433 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
434 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
435 if os.path.isfile(old_cp_path):
436 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
437 shutil.move(old_cp_path, new_cp_path)
438
439 # Checkpoint-related utilities
440
441 def get_checkpoint_path(self, checkpoint_id, name, path=''):
442 """find the path to a checkpoint"""
443 path = path.strip('/')
444 basename, ext = os.path.splitext(name)
445 filename = u"{name}-{checkpoint_id}{ext}".format(
446 name=basename,
447 checkpoint_id=checkpoint_id,
448 ext=ext,
449 )
450 os_path = self._get_os_path(path=path)
451 cp_dir = os.path.join(os_path, self.checkpoint_dir)
452 ensure_dir_exists(cp_dir)
453 cp_path = os.path.join(cp_dir, filename)
454 return cp_path
455
456 def get_checkpoint_model(self, checkpoint_id, name, path=''):
457 """construct the info dict for a given checkpoint"""
458 path = path.strip('/')
459 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
460 stats = os.stat(cp_path)
461 last_modified = tz.utcfromtimestamp(stats.st_mtime)
462 info = dict(
463 id = checkpoint_id,
464 last_modified = last_modified,
465 )
466 return info
467
468 # public checkpoint API
469
470 def create_checkpoint(self, name, path=''):
471 """Create a checkpoint from the current state of a file"""
472 path = path.strip('/')
473 src_path = self._get_os_path(name, path)
474 # only the one checkpoint ID:
475 checkpoint_id = u"checkpoint"
476 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
477 self.log.debug("creating checkpoint for %s", name)
478 self._copy(src_path, cp_path)
479
480 # return the checkpoint info
481 return self.get_checkpoint_model(checkpoint_id, name, path)
482
483 def list_checkpoints(self, name, path=''):
484 """list the checkpoints for a given file
485
486 This contents manager currently only supports one checkpoint per file.
487 """
488 path = path.strip('/')
489 checkpoint_id = "checkpoint"
490 os_path = self.get_checkpoint_path(checkpoint_id, name, path)
491 if not os.path.exists(os_path):
492 return []
493 else:
494 return [self.get_checkpoint_model(checkpoint_id, name, path)]
495
496
497 def restore_checkpoint(self, checkpoint_id, name, path=''):
498 """restore a file to a checkpointed state"""
499 path = path.strip('/')
500 self.log.info("restoring %s from checkpoint %s", name, checkpoint_id)
501 nb_path = self._get_os_path(name, path)
502 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
503 if not os.path.isfile(cp_path):
504 self.log.debug("checkpoint file does not exist: %s", cp_path)
505 raise web.HTTPError(404,
506 u'checkpoint does not exist: %s-%s' % (name, checkpoint_id)
507 )
508 # ensure notebook is readable (never restore from an unreadable notebook)
509 if cp_path.endswith('.ipynb'):
510 with io.open(cp_path, 'r', encoding='utf-8') as f:
511 current.read(f, u'json')
512 self._copy(cp_path, nb_path)
513 self.log.debug("copying %s -> %s", cp_path, nb_path)
514
515 def delete_checkpoint(self, checkpoint_id, name, path=''):
516 """delete a file's checkpoint"""
517 path = path.strip('/')
518 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
519 if not os.path.isfile(cp_path):
520 raise web.HTTPError(404,
521 u'Checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
522 )
523 self.log.debug("unlinking %s", cp_path)
524 os.unlink(cp_path)
525
526 def info_string(self):
527 return "Serving notebooks from local directory: %s" % self.root_dir
528
529 def get_kernel_path(self, name, path='', model=None):
530 """Return the initial working dir a kernel associated with a given notebook"""
531 return os.path.join(self.root_dir, path)
@@ -0,0 +1,286 b''
1 """Tornado handlers for the contents web service."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 import json
7
8 from tornado import web
9
10 from IPython.html.utils import url_path_join, url_escape
11 from IPython.utils.jsonutil import date_default
12
13 from IPython.html.base.handlers import (IPythonHandler, json_errors,
14 file_path_regex, path_regex,
15 file_name_regex)
16
17
18 def sort_key(model):
19 """key function for case-insensitive sort by name and type"""
20 iname = model['name'].lower()
21 type_key = {
22 'directory' : '0',
23 'notebook' : '1',
24 'file' : '2',
25 }.get(model['type'], '9')
26 return u'%s%s' % (type_key, iname)
27
28 class ContentsHandler(IPythonHandler):
29
30 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
31
32 def location_url(self, name, path):
33 """Return the full URL location of a file.
34
35 Parameters
36 ----------
37 name : unicode
38 The base name of the file, such as "foo.ipynb".
39 path : unicode
40 The API path of the file, such as "foo/bar".
41 """
42 return url_escape(url_path_join(
43 self.base_url, 'api', 'contents', path, name
44 ))
45
46 def _finish_model(self, model, location=True):
47 """Finish a JSON request with a model, setting relevant headers, etc."""
48 if location:
49 location = self.location_url(model['name'], model['path'])
50 self.set_header('Location', location)
51 self.set_header('Last-Modified', model['last_modified'])
52 self.finish(json.dumps(model, default=date_default))
53
54 @web.authenticated
55 @json_errors
56 def get(self, path='', name=None):
57 """Return a model for a file or directory.
58
59 A directory model contains a list of models (without content)
60 of the files and directories it contains.
61 """
62 path = path or ''
63 model = self.contents_manager.get_model(name=name, path=path)
64 if model['type'] == 'directory':
65 # group listing by type, then by name (case-insensitive)
66 # FIXME: sorting should be done in the frontends
67 model['content'].sort(key=sort_key)
68 self._finish_model(model, location=False)
69
70 @web.authenticated
71 @json_errors
72 def patch(self, path='', name=None):
73 """PATCH renames a notebook without re-uploading content."""
74 cm = self.contents_manager
75 if name is None:
76 raise web.HTTPError(400, u'Filename missing')
77 model = self.get_json_body()
78 if model is None:
79 raise web.HTTPError(400, u'JSON body missing')
80 model = cm.update(model, name, path)
81 self._finish_model(model)
82
83 def _copy(self, copy_from, path, copy_to=None):
84 """Copy a file, optionally specifying the new name.
85 """
86 self.log.info(u"Copying {copy_from} to {path}/{copy_to}".format(
87 copy_from=copy_from,
88 path=path,
89 copy_to=copy_to or '',
90 ))
91 model = self.contents_manager.copy(copy_from, copy_to, path)
92 self.set_status(201)
93 self._finish_model(model)
94
95 def _upload(self, model, path, name=None):
96 """Handle upload of a new file
97
98 If name specified, create it in path/name,
99 otherwise create a new untitled file in path.
100 """
101 self.log.info(u"Uploading file to %s/%s", path, name or '')
102 if name:
103 model['name'] = name
104
105 model = self.contents_manager.create_file(model, path)
106 self.set_status(201)
107 self._finish_model(model)
108
109 def _create_empty_file(self, path, name=None, ext='.ipynb'):
110 """Create an empty file in path
111
112 If name specified, create it in path/name.
113 """
114 self.log.info(u"Creating new file in %s/%s", path, name or '')
115 model = {}
116 if name:
117 model['name'] = name
118 model = self.contents_manager.create_file(model, path=path, ext=ext)
119 self.set_status(201)
120 self._finish_model(model)
121
122 def _save(self, model, path, name):
123 """Save an existing file."""
124 self.log.info(u"Saving file at %s/%s", path, name)
125 model = self.contents_manager.save(model, name, path)
126 if model['path'] != path.strip('/') or model['name'] != name:
127 # a rename happened, set Location header
128 location = True
129 else:
130 location = False
131 self._finish_model(model, location)
132
133 @web.authenticated
134 @json_errors
135 def post(self, path='', name=None):
136 """Create a new file or directory in the specified path.
137
138 POST creates new files or directories. The server always decides on the name.
139
140 POST /api/contents/path
141 New untitled notebook in path. If content specified, upload a
142 notebook, otherwise start empty.
143 POST /api/contents/path
144 with body {"copy_from" : "OtherNotebook.ipynb"}
145 New copy of OtherNotebook in path
146 """
147
148 if name is not None:
149 path = u'{}/{}'.format(path, name)
150
151 cm = self.contents_manager
152
153 if cm.file_exists(path):
154 raise web.HTTPError(400, "Cannot POST to existing files, use PUT instead.")
155
156 if not cm.path_exists(path):
157 raise web.HTTPError(404, "No such directory: %s" % path)
158
159 model = self.get_json_body()
160
161 if model is not None:
162 copy_from = model.get('copy_from')
163 ext = model.get('ext', '.ipynb')
164 if model.get('content') is not None:
165 if copy_from:
166 raise web.HTTPError(400, "Can't upload and copy at the same time.")
167 self._upload(model, path)
168 elif copy_from:
169 self._copy(copy_from, path)
170 else:
171 self._create_empty_file(path, ext=ext)
172 else:
173 self._create_empty_file(path)
174
175 @web.authenticated
176 @json_errors
177 def put(self, path='', name=None):
178 """Saves the file in the location specified by name and path.
179
180 PUT is very similar to POST, but the requester specifies the name,
181 whereas with POST, the server picks the name.
182
183 PUT /api/contents/path/Name.ipynb
184 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
185 in `content` key of JSON request body. If content is not specified,
186 create a new empty notebook.
187 PUT /api/contents/path/Name.ipynb
188 with JSON body::
189
190 {
191 "copy_from" : "[path/to/]OtherNotebook.ipynb"
192 }
193
194 Copy OtherNotebook to Name
195 """
196 if name is None:
197 raise web.HTTPError(400, "name must be specified with PUT.")
198
199 model = self.get_json_body()
200 if model:
201 copy_from = model.get('copy_from')
202 if copy_from:
203 if model.get('content'):
204 raise web.HTTPError(400, "Can't upload and copy at the same time.")
205 self._copy(copy_from, path, name)
206 elif self.contents_manager.file_exists(name, path):
207 self._save(model, path, name)
208 else:
209 self._upload(model, path, name)
210 else:
211 self._create_empty_file(path, name)
212
213 @web.authenticated
214 @json_errors
215 def delete(self, path='', name=None):
216 """delete a file in the given path"""
217 cm = self.contents_manager
218 self.log.warn('delete %s:%s', path, name)
219 cm.delete(name, path)
220 self.set_status(204)
221 self.finish()
222
223
224 class CheckpointsHandler(IPythonHandler):
225
226 SUPPORTED_METHODS = ('GET', 'POST')
227
228 @web.authenticated
229 @json_errors
230 def get(self, path='', name=None):
231 """get lists checkpoints for a file"""
232 cm = self.contents_manager
233 checkpoints = cm.list_checkpoints(name, path)
234 data = json.dumps(checkpoints, default=date_default)
235 self.finish(data)
236
237 @web.authenticated
238 @json_errors
239 def post(self, path='', name=None):
240 """post creates a new checkpoint"""
241 cm = self.contents_manager
242 checkpoint = cm.create_checkpoint(name, path)
243 data = json.dumps(checkpoint, default=date_default)
244 location = url_path_join(self.base_url, 'api/contents',
245 path, name, 'checkpoints', checkpoint['id'])
246 self.set_header('Location', url_escape(location))
247 self.set_status(201)
248 self.finish(data)
249
250
251 class ModifyCheckpointsHandler(IPythonHandler):
252
253 SUPPORTED_METHODS = ('POST', 'DELETE')
254
255 @web.authenticated
256 @json_errors
257 def post(self, path, name, checkpoint_id):
258 """post restores a file from a checkpoint"""
259 cm = self.contents_manager
260 cm.restore_checkpoint(checkpoint_id, name, path)
261 self.set_status(204)
262 self.finish()
263
264 @web.authenticated
265 @json_errors
266 def delete(self, path, name, checkpoint_id):
267 """delete clears a checkpoint for a given file"""
268 cm = self.contents_manager
269 cm.delete_checkpoint(checkpoint_id, name, path)
270 self.set_status(204)
271 self.finish()
272
273 #-----------------------------------------------------------------------------
274 # URL to handler mappings
275 #-----------------------------------------------------------------------------
276
277
278 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
279
280 default_handlers = [
281 (r"/api/contents%s/checkpoints" % file_path_regex, CheckpointsHandler),
282 (r"/api/contents%s/checkpoints/%s" % (file_path_regex, _checkpoint_id_regex),
283 ModifyCheckpointsHandler),
284 (r"/api/contents%s" % file_path_regex, ContentsHandler),
285 (r"/api/contents%s" % path_regex, ContentsHandler),
286 ]
@@ -0,0 +1,333 b''
1 """A base class for contents managers."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 from fnmatch import fnmatch
7 import itertools
8 import os
9
10 from tornado.web import HTTPError
11
12 from IPython.config.configurable import LoggingConfigurable
13 from IPython.nbformat import current, sign
14 from IPython.utils.traitlets import Instance, Unicode, List
15
16
17 class ContentsManager(LoggingConfigurable):
18 """Base class for serving files and directories.
19
20 This serves any text or binary file,
21 as well as directories,
22 with special handling for JSON notebook documents.
23
24 Most APIs take a path argument,
25 which is always an API-style unicode path,
26 and always refers to a directory.
27
28 - unicode, not url-escaped
29 - '/'-separated
30 - leading and trailing '/' will be stripped
31 - if unspecified, path defaults to '',
32 indicating the root path.
33
34 name is also unicode, and refers to a specfic target:
35
36 - unicode, not url-escaped
37 - must not contain '/'
38 - It refers to an individual filename
39 - It may refer to a directory name,
40 in the case of listing or creating directories.
41
42 """
43
44 notary = Instance(sign.NotebookNotary)
45 def _notary_default(self):
46 return sign.NotebookNotary(parent=self)
47
48 hide_globs = List(Unicode, [
49 u'__pycache__', '*.pyc', '*.pyo',
50 '.DS_Store', '*.so', '*.dylib', '*~',
51 ], config=True, help="""
52 Glob patterns to hide in file and directory listings.
53 """)
54
55 untitled_notebook = Unicode("Untitled", config=True,
56 help="The base name used when creating untitled notebooks."
57 )
58
59 untitled_file = Unicode("untitled", config=True,
60 help="The base name used when creating untitled files."
61 )
62
63 untitled_directory = Unicode("Untitled Folder", config=True,
64 help="The base name used when creating untitled directories."
65 )
66
67 # ContentsManager API part 1: methods that must be
68 # implemented in subclasses.
69
70 def path_exists(self, path):
71 """Does the API-style path (directory) actually exist?
72
73 Like os.path.isdir
74
75 Override this method in subclasses.
76
77 Parameters
78 ----------
79 path : string
80 The path to check
81
82 Returns
83 -------
84 exists : bool
85 Whether the path does indeed exist.
86 """
87 raise NotImplementedError
88
89 def is_hidden(self, path):
90 """Does the API style path correspond to a hidden directory or file?
91
92 Parameters
93 ----------
94 path : string
95 The path to check. This is an API path (`/` separated,
96 relative to root dir).
97
98 Returns
99 -------
100 hidden : bool
101 Whether the path is hidden.
102
103 """
104 raise NotImplementedError
105
106 def file_exists(self, name, path=''):
107 """Does a file exist at the given name and path?
108
109 Like os.path.isfile
110
111 Override this method in subclasses.
112
113 Parameters
114 ----------
115 name : string
116 The name of the file you are checking.
117 path : string
118 The relative path to the file's directory (with '/' as separator)
119
120 Returns
121 -------
122 exists : bool
123 Whether the file exists.
124 """
125 raise NotImplementedError('must be implemented in a subclass')
126
127 def exists(self, name, path=''):
128 """Does a file or directory exist at the given name and path?
129
130 Like os.path.exists
131
132 Parameters
133 ----------
134 name : string
135 The name of the file you are checking.
136 path : string
137 The relative path to the file's directory (with '/' as separator)
138
139 Returns
140 -------
141 exists : bool
142 Whether the target exists.
143 """
144 return self.file_exists(name, path) or self.path_exists("%s/%s" % (path, name))
145
146 def get_model(self, name, path='', content=True):
147 """Get the model of a file or directory with or without content."""
148 raise NotImplementedError('must be implemented in a subclass')
149
150 def save(self, model, name, path=''):
151 """Save the file or directory and return the model with no content."""
152 raise NotImplementedError('must be implemented in a subclass')
153
154 def update(self, model, name, path=''):
155 """Update the file or directory and return the model with no content.
156
157 For use in PATCH requests, to enable renaming a file without
158 re-uploading its contents. Only used for renaming at the moment.
159 """
160 raise NotImplementedError('must be implemented in a subclass')
161
162 def delete(self, name, path=''):
163 """Delete file or directory by name and path."""
164 raise NotImplementedError('must be implemented in a subclass')
165
166 def create_checkpoint(self, name, path=''):
167 """Create a checkpoint of the current state of a file
168
169 Returns a checkpoint_id for the new checkpoint.
170 """
171 raise NotImplementedError("must be implemented in a subclass")
172
173 def list_checkpoints(self, name, path=''):
174 """Return a list of checkpoints for a given file"""
175 return []
176
177 def restore_checkpoint(self, checkpoint_id, name, path=''):
178 """Restore a file from one of its checkpoints"""
179 raise NotImplementedError("must be implemented in a subclass")
180
181 def delete_checkpoint(self, checkpoint_id, name, path=''):
182 """delete a checkpoint for a file"""
183 raise NotImplementedError("must be implemented in a subclass")
184
185 # ContentsManager API part 2: methods that have useable default
186 # implementations, but can be overridden in subclasses.
187
188 def info_string(self):
189 return "Serving contents"
190
191 def get_kernel_path(self, name, path='', model=None):
192 """ Return the path to start kernel in """
193 return path
194
195 def increment_filename(self, filename, path=''):
196 """Increment a filename until it is unique.
197
198 Parameters
199 ----------
200 filename : unicode
201 The name of a file, including extension
202 path : unicode
203 The API path of the target's directory
204
205 Returns
206 -------
207 name : unicode
208 A filename that is unique, based on the input filename.
209 """
210 path = path.strip('/')
211 basename, ext = os.path.splitext(filename)
212 for i in itertools.count():
213 name = u'{basename}{i}{ext}'.format(basename=basename, i=i,
214 ext=ext)
215 if not self.file_exists(name, path):
216 break
217 return name
218
219 def create_file(self, model=None, path='', ext='.ipynb'):
220 """Create a new file or directory and return its model with no content."""
221 path = path.strip('/')
222 if model is None:
223 model = {}
224 if 'content' not in model and model.get('type', None) != 'directory':
225 if ext == '.ipynb':
226 metadata = current.new_metadata(name=u'')
227 model['content'] = current.new_notebook(metadata=metadata)
228 model['type'] = 'notebook'
229 model['format'] = 'json'
230 else:
231 model['content'] = ''
232 model['type'] = 'file'
233 model['format'] = 'text'
234 if 'name' not in model:
235 if model['type'] == 'directory':
236 untitled = self.untitled_directory
237 elif model['type'] == 'notebook':
238 untitled = self.untitled_notebook
239 elif model['type'] == 'file':
240 untitled = self.untitled_file
241 else:
242 raise HTTPError(400, "Unexpected model type: %r" % model['type'])
243 model['name'] = self.increment_filename(untitled + ext, path)
244
245 model['path'] = path
246 model = self.save(model, model['name'], model['path'])
247 return model
248
249 def copy(self, from_name, to_name=None, path=''):
250 """Copy an existing file and return its new model.
251
252 If to_name not specified, increment `from_name-Copy#.ext`.
253
254 copy_from can be a full path to a file,
255 or just a base name. If a base name, `path` is used.
256 """
257 path = path.strip('/')
258 if '/' in from_name:
259 from_path, from_name = from_name.rsplit('/', 1)
260 else:
261 from_path = path
262 model = self.get_model(from_name, from_path)
263 if model['type'] == 'directory':
264 raise HTTPError(400, "Can't copy directories")
265 if not to_name:
266 base, ext = os.path.splitext(from_name)
267 copy_name = u'{0}-Copy{1}'.format(base, ext)
268 to_name = self.increment_filename(copy_name, path)
269 model['name'] = to_name
270 model['path'] = path
271 model = self.save(model, to_name, path)
272 return model
273
274 def log_info(self):
275 self.log.info(self.info_string())
276
277 def trust_notebook(self, name, path=''):
278 """Explicitly trust a notebook
279
280 Parameters
281 ----------
282 name : string
283 The filename of the notebook
284 path : string
285 The notebook's directory
286 """
287 model = self.get_model(name, path)
288 nb = model['content']
289 self.log.warn("Trusting notebook %s/%s", path, name)
290 self.notary.mark_cells(nb, True)
291 self.save(model, name, path)
292
293 def check_and_sign(self, nb, name='', path=''):
294 """Check for trusted cells, and sign the notebook.
295
296 Called as a part of saving notebooks.
297
298 Parameters
299 ----------
300 nb : dict
301 The notebook object (in nbformat.current format)
302 name : string
303 The filename of the notebook (for logging)
304 path : string
305 The notebook's directory (for logging)
306 """
307 if self.notary.check_cells(nb):
308 self.notary.sign(nb)
309 else:
310 self.log.warn("Saving untrusted notebook %s/%s", path, name)
311
312 def mark_trusted_cells(self, nb, name='', path=''):
313 """Mark cells as trusted if the notebook signature matches.
314
315 Called as a part of loading notebooks.
316
317 Parameters
318 ----------
319 nb : dict
320 The notebook object (in nbformat.current format)
321 name : string
322 The filename of the notebook (for logging)
323 path : string
324 The notebook's directory (for logging)
325 """
326 trusted = self.notary.check_signature(nb)
327 if not trusted:
328 self.log.warn("Notebook %s/%s is not trusted", path, name)
329 self.notary.mark_cells(nb, trusted)
330
331 def should_list(self, name):
332 """Should this file/directory name be displayed in a listing?"""
333 return not any(fnmatch(name, glob) for glob in self.hide_globs)
@@ -0,0 +1,91 b''
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
4 define([
5 'base/js/namespace',
6 'jquery',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
9 "use strict";
10
11 var KernelSelector = function(selector, notebook) {
12 this.selector = selector;
13 this.notebook = notebook;
14 this.events = notebook.events;
15 this.current_selection = notebook.default_kernel_name;
16 this.kernelspecs = {};
17 if (this.selector !== undefined) {
18 this.element = $(selector);
19 this.request_kernelspecs();
20 }
21 this.bind_events();
22 // Make the object globally available for user convenience & inspection
23 IPython.kernelselector = this;
24 };
25
26 KernelSelector.prototype.request_kernelspecs = function() {
27 var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs');
28 $.ajax(url, {success: $.proxy(this._got_kernelspecs, this)});
29 };
30
31 KernelSelector.prototype._got_kernelspecs = function(data, status, xhr) {
32 this.kernelspecs = {};
33 var menu = this.element.find("#kernel_selector");
34 var change_kernel_submenu = $("#menu-change-kernel-submenu");
35 for (var i = 0; i < data.length; i++) {
36 var ks = data[i];
37 this.kernelspecs[ks.name] = ks;
38 var ksentry = $("<li>").attr("id", "kernel-" +ks.name).append($('<a>')
39 .attr('href', '#')
40 .click($.proxy(this.change_kernel, this, ks.name))
41 .text(ks.display_name));
42 menu.append(ksentry);
43
44 var ks_submenu_entry = $("<li>").attr("id", "kernel-submenu-"+ks.name).append($('<a>')
45 .attr('href', '#')
46 .click($.proxy(this.change_kernel, this, ks.name))
47 .text(ks.display_name));
48 change_kernel_submenu.append(ks_submenu_entry);
49 }
50 };
51
52 KernelSelector.prototype.change_kernel = function(kernel_name) {
53 if (kernel_name === this.current_selection) {
54 return;
55 }
56 var ks = this.kernelspecs[kernel_name];
57 try {
58 this.notebook.start_session(kernel_name);
59 } catch (e) {
60 if (e.name === 'SessionAlreadyStarting') {
61 console.log("Cannot change kernel while waiting for pending session start.");
62 } else {
63 // unhandled error
64 throw e;
65 }
66 // only trigger spec_changed if change was successful
67 return;
68 }
69 this.events.trigger('spec_changed.Kernel', ks);
70 };
71
72 KernelSelector.prototype.bind_events = function() {
73 var that = this;
74 this.events.on('spec_changed.Kernel', function(event, data) {
75 that.current_selection = data.name;
76 that.element.find("#current_kernel_spec").find('.kernel_name').text(data.display_name);
77 });
78
79 this.events.on('started.Session', function(events, session) {
80 if (session.kernel_name !== that.current_selection) {
81 // If we created a 'python' session, we only know if it's Python
82 // 3 or 2 on the server's reply, so we fire the event again to
83 // set things up.
84 var ks = that.kernelspecs[session.kernel_name];
85 that.events.trigger('spec_changed.Kernel', ks);
86 }
87 });
88 };
89
90 return {'KernelSelector': KernelSelector};
91 });
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 120000
NO CONTENT: new file 120000
The requested commit or file is too big and content was truncated. Show full diff
@@ -7,6 +7,7 b' docs/source/api/generated'
7 docs/source/config/options
7 docs/source/config/options
8 docs/gh-pages
8 docs/gh-pages
9 IPython/html/notebook/static/mathjax
9 IPython/html/notebook/static/mathjax
10 IPython/html/static/style/*.map
10 *.py[co]
11 *.py[co]
11 __pycache__
12 __pycache__
12 *.egg-info
13 *.egg-info
@@ -13,8 +13,10 b' before_install:'
13 # Pierre Carrier's PPA for PhantomJS and CasperJS
13 # Pierre Carrier's PPA for PhantomJS and CasperJS
14 - time sudo add-apt-repository -y ppa:pcarrier/ppa
14 - time sudo add-apt-repository -y ppa:pcarrier/ppa
15 - time sudo apt-get update
15 - time sudo apt-get update
16 - time sudo apt-get install pandoc casperjs nodejs libzmq3-dev
16 - time sudo apt-get install pandoc casperjs libzmq3-dev
17 - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests mock pyzmq jsonschema jsonpointer
17 # pin tornado < 4 for js tests while phantom is on super old webkit
18 - if [[ $GROUP == 'js' ]]; then pip install 'tornado<4'; fi
19 - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests mock pyzmq jsonschema jsonpointer mistune
18 install:
20 install:
19 - time python setup.py install -q
21 - time python setup.py install -q
20 script:
22 script:
@@ -1,31 +1,11 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """A base class for objects that are configurable."""
3 A base class for objects that are configurable.
4
3
5 Inheritance diagram:
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 .. inheritance-diagram:: IPython.config.configurable
8 :parts: 3
9
10 Authors:
11
12 * Brian Granger
13 * Fernando Perez
14 * Min RK
15 """
16 from __future__ import print_function
7 from __future__ import print_function
17
8
18 #-----------------------------------------------------------------------------
19 # Copyright (C) 2008-2011 The IPython Development Team
20 #
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
23 #-----------------------------------------------------------------------------
24
25 #-----------------------------------------------------------------------------
26 # Imports
27 #-----------------------------------------------------------------------------
28
29 import logging
9 import logging
30 from copy import deepcopy
10 from copy import deepcopy
31
11
@@ -375,16 +355,12 b' class LoggingConfigurable(Configurable):'
375 """A parent class for Configurables that log.
355 """A parent class for Configurables that log.
376
356
377 Subclasses have a log trait, and the default behavior
357 Subclasses have a log trait, and the default behavior
378 is to get the logger from the currently running Application
358 is to get the logger from the currently running Application.
379 via Application.instance().log.
380 """
359 """
381
360
382 log = Instance('logging.Logger')
361 log = Instance('logging.Logger')
383 def _log_default(self):
362 def _log_default(self):
384 from IPython.config.application import Application
363 from IPython.utils import log
385 if Application.initialized():
364 return log.get_logger()
386 return Application.instance().log
387 else:
388 return logging.getLogger()
389
365
390
366
@@ -1,27 +1,8 b''
1 """A simple configuration system.
1 # encoding: utf-8
2 """A simple configuration system."""
2
3
3 Inheritance diagram:
4 # Copyright (c) IPython Development Team.
4
5 # Distributed under the terms of the Modified BSD License.
5 .. inheritance-diagram:: IPython.config.loader
6 :parts: 3
7
8 Authors
9 -------
10 * Brian Granger
11 * Fernando Perez
12 * Min RK
13 """
14
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
21
22 #-----------------------------------------------------------------------------
23 # Imports
24 #-----------------------------------------------------------------------------
25
6
26 import argparse
7 import argparse
27 import copy
8 import copy
@@ -308,11 +289,8 b' class ConfigLoader(object):'
308 """
289 """
309
290
310 def _log_default(self):
291 def _log_default(self):
311 from IPython.config.application import Application
292 from IPython.utils.log import get_logger
312 if Application.initialized():
293 return get_logger()
313 return Application.instance().log
314 else:
315 return logging.getLogger()
316
294
317 def __init__(self, log=None):
295 def __init__(self, log=None):
318 """A base class for config loaders.
296 """A base class for config loaders.
@@ -165,8 +165,6 b' class IPythonConsoleApp(ConnectionFileMixin):'
165 if argv is None:
165 if argv is None:
166 argv = sys.argv[1:]
166 argv = sys.argv[1:]
167 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
167 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
168 # kernel should inherit default config file from frontend
169 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
170
168
171 def init_connection_file(self):
169 def init_connection_file(self):
172 """find the connection file, and load the info if found.
170 """find the connection file, and load the info if found.
@@ -289,6 +287,7 b' class IPythonConsoleApp(ConnectionFileMixin):'
289 try:
287 try:
290 self.kernel_manager = self.kernel_manager_class(
288 self.kernel_manager = self.kernel_manager_class(
291 ip=self.ip,
289 ip=self.ip,
290 session=self.session,
292 transport=self.transport,
291 transport=self.transport,
293 shell_port=self.shell_port,
292 shell_port=self.shell_port,
294 iopub_port=self.iopub_port,
293 iopub_port=self.iopub_port,
@@ -326,6 +325,7 b' class IPythonConsoleApp(ConnectionFileMixin):'
326 self.kernel_client = self.kernel_manager.client()
325 self.kernel_client = self.kernel_manager.client()
327 else:
326 else:
328 self.kernel_client = self.kernel_client_class(
327 self.kernel_client = self.kernel_client_class(
328 session=self.session,
329 ip=self.ip,
329 ip=self.ip,
330 transport=self.transport,
330 transport=self.transport,
331 shell_port=self.shell_port,
331 shell_port=self.shell_port,
@@ -80,6 +80,7 b' from IPython.core.error import TryNext'
80 from IPython.core.inputsplitter import ESC_MAGIC
80 from IPython.core.inputsplitter import ESC_MAGIC
81 from IPython.utils import generics
81 from IPython.utils import generics
82 from IPython.utils import io
82 from IPython.utils import io
83 from IPython.utils.decorators import undoc
83 from IPython.utils.dir2 import dir2
84 from IPython.utils.dir2 import dir2
84 from IPython.utils.process import arg_split
85 from IPython.utils.process import arg_split
85 from IPython.utils.py3compat import builtin_mod, string_types
86 from IPython.utils.py3compat import builtin_mod, string_types
@@ -216,7 +217,7 b' def penalize_magics_key(word):'
216 return word
217 return word
217
218
218
219
219
220 @undoc
220 class Bunch(object): pass
221 class Bunch(object): pass
221
222
222
223
@@ -865,6 +866,7 b' class IPCompleter(Completer):'
865 return argMatches
866 return argMatches
866
867
867 def dict_key_matches(self, text):
868 def dict_key_matches(self, text):
869 "Match string keys in a dictionary, after e.g. 'foo[' "
868 def get_keys(obj):
870 def get_keys(obj):
869 # Only allow completion for known in-memory dict-like types
871 # Only allow completion for known in-memory dict-like types
870 if isinstance(obj, dict) or\
872 if isinstance(obj, dict) or\
@@ -1010,9 +1012,6 b' class IPCompleter(Completer):'
1010 def complete(self, text=None, line_buffer=None, cursor_pos=None):
1012 def complete(self, text=None, line_buffer=None, cursor_pos=None):
1011 """Find completions for the given text and line context.
1013 """Find completions for the given text and line context.
1012
1014
1013 This is called successively with state == 0, 1, 2, ... until it
1014 returns None. The completion should begin with 'text'.
1015
1016 Note that both the text and the line_buffer are optional, but at least
1015 Note that both the text and the line_buffer are optional, but at least
1017 one of them must be given.
1016 one of them must be given.
1018
1017
@@ -26,7 +26,13 b' from IPython.core.formatters import _safe_get_formatter_method'
26 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
26 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
27 unicode_type)
27 unicode_type)
28 from IPython.testing.skipdoctest import skip_doctest
28 from IPython.testing.skipdoctest import skip_doctest
29 from .displaypub import publish_display_data
29
30 __all__ = ['display', 'display_pretty', 'display_html', 'display_markdown',
31 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
32 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
33 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'JSON', 'Javascript',
34 'Image', 'clear_output', 'set_matplotlib_formats', 'set_matplotlib_close',
35 'publish_display_data']
30
36
31 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
32 # utility functions
38 # utility functions
@@ -78,6 +84,48 b' def _display_mimetype(mimetype, objs, raw=False, metadata=None):'
78 # Main functions
84 # Main functions
79 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
80
86
87 def publish_display_data(data, metadata=None, source=None):
88 """Publish data and metadata to all frontends.
89
90 See the ``display_data`` message in the messaging documentation for
91 more details about this message type.
92
93 The following MIME types are currently implemented:
94
95 * text/plain
96 * text/html
97 * text/markdown
98 * text/latex
99 * application/json
100 * application/javascript
101 * image/png
102 * image/jpeg
103 * image/svg+xml
104
105 Parameters
106 ----------
107 data : dict
108 A dictionary having keys that are valid MIME types (like
109 'text/plain' or 'image/svg+xml') and values that are the data for
110 that MIME type. The data itself must be a JSON'able data
111 structure. Minimally all data should have the 'text/plain' data,
112 which can be displayed by all frontends. If more than the plain
113 text is given, it is up to the frontend to decide which
114 representation to use.
115 metadata : dict
116 A dictionary for metadata related to the data. This can contain
117 arbitrary key, value pairs that frontends can use to interpret
118 the data. mime-type keys matching those in data can be used
119 to specify metadata about particular representations.
120 source : str, deprecated
121 Unused.
122 """
123 from IPython.core.interactiveshell import InteractiveShell
124 InteractiveShell.instance().display_pub.publish(
125 data=data,
126 metadata=metadata,
127 )
128
81 def display(*objs, **kwargs):
129 def display(*objs, **kwargs):
82 """Display a Python object in all frontends.
130 """Display a Python object in all frontends.
83
131
@@ -19,9 +19,11 b' from __future__ import print_function'
19
19
20 from IPython.config.configurable import Configurable
20 from IPython.config.configurable import Configurable
21 from IPython.utils import io
21 from IPython.utils import io
22 from IPython.utils.py3compat import string_types
23 from IPython.utils.traitlets import List
22 from IPython.utils.traitlets import List
24
23
24 # This used to be defined here - it is imported for backwards compatibility
25 from .display import publish_display_data
26
25 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
26 # Main payload class
28 # Main payload class
27 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
@@ -112,48 +114,3 b' class CapturingDisplayPublisher(DisplayPublisher):'
112
114
113 # empty the list, *do not* reassign a new list
115 # empty the list, *do not* reassign a new list
114 del self.outputs[:]
116 del self.outputs[:]
115
116
117 def publish_display_data(data, metadata=None, source=None):
118 """Publish data and metadata to all frontends.
119
120 See the ``display_data`` message in the messaging documentation for
121 more details about this message type.
122
123 The following MIME types are currently implemented:
124
125 * text/plain
126 * text/html
127 * text/markdown
128 * text/latex
129 * application/json
130 * application/javascript
131 * image/png
132 * image/jpeg
133 * image/svg+xml
134
135 Parameters
136 ----------
137 data : dict
138 A dictionary having keys that are valid MIME types (like
139 'text/plain' or 'image/svg+xml') and values that are the data for
140 that MIME type. The data itself must be a JSON'able data
141 structure. Minimally all data should have the 'text/plain' data,
142 which can be displayed by all frontends. If more than the plain
143 text is given, it is up to the frontend to decide which
144 representation to use.
145 metadata : dict
146 A dictionary for metadata related to the data. This can contain
147 arbitrary key, value pairs that frontends can use to interpret
148 the data. mime-type keys matching those in data can be used
149 to specify metadata about particular representations.
150 source : str, deprecated
151 Unused.
152 """
153 from IPython.core.interactiveshell import InteractiveShell
154 InteractiveShell.instance().display_pub.publish(
155 data=data,
156 metadata=metadata,
157 )
158
159
@@ -736,12 +736,13 b' class InteractiveShell(SingletonConfigurable):'
736 # stdlib venv may symlink sys.executable, so we can't use realpath.
736 # stdlib venv may symlink sys.executable, so we can't use realpath.
737 # but others can symlink *to* the venv Python, so we can't just use sys.executable.
737 # but others can symlink *to* the venv Python, so we can't just use sys.executable.
738 # So we just check every item in the symlink tree (generally <= 3)
738 # So we just check every item in the symlink tree (generally <= 3)
739 p = sys.executable
739 p = os.path.normcase(sys.executable)
740 paths = [p]
740 paths = [p]
741 while os.path.islink(p):
741 while os.path.islink(p):
742 p = os.path.join(os.path.dirname(p), os.readlink(p))
742 p = os.path.normcase(os.path.join(os.path.dirname(p), os.readlink(p)))
743 paths.append(p)
743 paths.append(p)
744 if any(p.startswith(os.environ['VIRTUAL_ENV']) for p in paths):
744 p_venv = os.path.normcase(os.environ['VIRTUAL_ENV'])
745 if any(p.startswith(p_venv) for p in paths):
745 # Running properly in the virtualenv, don't need to do anything
746 # Running properly in the virtualenv, don't need to do anything
746 return
747 return
747
748
@@ -910,7 +911,8 b' class InteractiveShell(SingletonConfigurable):'
910 try:
911 try:
911 main_mod = self._main_mod_cache[filename]
912 main_mod = self._main_mod_cache[filename]
912 except KeyError:
913 except KeyError:
913 main_mod = self._main_mod_cache[filename] = types.ModuleType(modname,
914 main_mod = self._main_mod_cache[filename] = types.ModuleType(
915 py3compat.cast_bytes_py2(modname),
914 doc="Module created for script run in IPython")
916 doc="Module created for script run in IPython")
915 else:
917 else:
916 main_mod.__dict__.clear()
918 main_mod.__dict__.clear()
@@ -1735,7 +1737,7 b' class InteractiveShell(SingletonConfigurable):'
1735 This hook should be used sparingly, only in places which are not likely
1737 This hook should be used sparingly, only in places which are not likely
1736 to be true IPython errors.
1738 to be true IPython errors.
1737 """
1739 """
1738 self.showtraceback((etype,value,tb),tb_offset=0)
1740 self.showtraceback((etype, value, tb), tb_offset=0)
1739
1741
1740 def _get_exc_info(self, exc_tuple=None):
1742 def _get_exc_info(self, exc_tuple=None):
1741 """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc.
1743 """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc.
@@ -1776,7 +1778,7 b' class InteractiveShell(SingletonConfigurable):'
1776 """
1778 """
1777 self.write_err("UsageError: %s" % exc)
1779 self.write_err("UsageError: %s" % exc)
1778
1780
1779 def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None,
1781 def showtraceback(self, exc_tuple=None, filename=None, tb_offset=None,
1780 exception_only=False):
1782 exception_only=False):
1781 """Display the exception that just occurred.
1783 """Display the exception that just occurred.
1782
1784
@@ -2918,10 +2920,9 b' class InteractiveShell(SingletonConfigurable):'
2918 False : successful execution.
2920 False : successful execution.
2919 True : an error occurred.
2921 True : an error occurred.
2920 """
2922 """
2921
2922 # Set our own excepthook in case the user code tries to call it
2923 # Set our own excepthook in case the user code tries to call it
2923 # directly, so that the IPython crash handler doesn't get triggered
2924 # directly, so that the IPython crash handler doesn't get triggered
2924 old_excepthook,sys.excepthook = sys.excepthook, self.excepthook
2925 old_excepthook, sys.excepthook = sys.excepthook, self.excepthook
2925
2926
2926 # we save the original sys.excepthook in the instance, in case config
2927 # we save the original sys.excepthook in the instance, in case config
2927 # code (such as magics) needs access to it.
2928 # code (such as magics) needs access to it.
@@ -2939,8 +2940,8 b' class InteractiveShell(SingletonConfigurable):'
2939 self.showtraceback(exception_only=True)
2940 self.showtraceback(exception_only=True)
2940 warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1)
2941 warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1)
2941 except self.custom_exceptions:
2942 except self.custom_exceptions:
2942 etype,value,tb = sys.exc_info()
2943 etype, value, tb = sys.exc_info()
2943 self.CustomTB(etype,value,tb)
2944 self.CustomTB(etype, value, tb)
2944 except:
2945 except:
2945 self.showtraceback()
2946 self.showtraceback()
2946 else:
2947 else:
@@ -3087,6 +3088,7 b' class InteractiveShell(SingletonConfigurable):'
3087 self.tempdirs.append(dirname)
3088 self.tempdirs.append(dirname)
3088
3089
3089 handle, filename = tempfile.mkstemp('.py', prefix, dir=dirname)
3090 handle, filename = tempfile.mkstemp('.py', prefix, dir=dirname)
3091 os.close(handle) # On Windows, there can only be one open handle on a file
3090 self.tempfiles.append(filename)
3092 self.tempfiles.append(filename)
3091
3093
3092 if data:
3094 if data:
@@ -193,6 +193,8 b' class ScriptMagics(Magics):'
193 else:
193 else:
194 raise
194 raise
195
195
196 if not cell.endswith('\n'):
197 cell += '\n'
196 cell = cell.encode('utf8', 'replace')
198 cell = cell.encode('utf8', 'replace')
197 if args.bg:
199 if args.bg:
198 self.bg_processes.append(p)
200 self.bg_processes.append(p)
@@ -6,6 +6,7 b''
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # stdlib
8 # stdlib
9 import io
9 import os
10 import os
10 import sys
11 import sys
11 import tempfile
12 import tempfile
@@ -124,7 +125,7 b' def test_history():'
124 # Cross testing: check that magic %save can get previous session.
125 # Cross testing: check that magic %save can get previous session.
125 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
126 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
126 ip.magic("save " + testfilename + " ~1/1-3")
127 ip.magic("save " + testfilename + " ~1/1-3")
127 with py3compat.open(testfilename, encoding='utf-8') as testfile:
128 with io.open(testfilename, encoding='utf-8') as testfile:
128 nt.assert_equal(testfile.read(),
129 nt.assert_equal(testfile.read(),
129 u"# coding: utf-8\n" + u"\n".join(hist)+u"\n")
130 u"# coding: utf-8\n" + u"\n".join(hist)+u"\n")
130
131
@@ -462,6 +462,21 b' class InteractiveShellTestCase(unittest.TestCase):'
462 ip.run_cell("d = 1/2", shell_futures=True)
462 ip.run_cell("d = 1/2", shell_futures=True)
463 self.assertEqual(ip.user_ns['d'], 0)
463 self.assertEqual(ip.user_ns['d'], 0)
464
464
465 def test_mktempfile(self):
466 filename = ip.mktempfile()
467 # Check that we can open the file again on Windows
468 with open(filename, 'w') as f:
469 f.write('abc')
470
471 filename = ip.mktempfile(data='blah')
472 with open(filename, 'r') as f:
473 self.assertEqual(f.read(), 'blah')
474
475 def test_new_main_mod(self):
476 # Smoketest to check that this accepts a unicode module name
477 name = u'jiefmw'
478 mod = ip.new_main_mod(u'%s.py' % name, name)
479 self.assertEqual(mod.__name__, name)
465
480
466 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
481 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
467
482
@@ -9,6 +9,7 b' from IPython.testing import tools as tt'
9 from IPython.testing.decorators import onlyif_unicode_paths
9 from IPython.testing.decorators import onlyif_unicode_paths
10 from IPython.utils.syspathcontext import prepended_to_syspath
10 from IPython.utils.syspathcontext import prepended_to_syspath
11 from IPython.utils.tempdir import TemporaryDirectory
11 from IPython.utils.tempdir import TemporaryDirectory
12 from IPython.utils.py3compat import PY3
12
13
13 ip = get_ipython()
14 ip = get_ipython()
14
15
@@ -147,3 +148,37 b' class SyntaxErrorTest(unittest.TestCase):'
147 except ValueError:
148 except ValueError:
148 with tt.AssertPrints('QWERTY'):
149 with tt.AssertPrints('QWERTY'):
149 ip.showsyntaxerror()
150 ip.showsyntaxerror()
151
152
153 class Python3ChainedExceptionsTest(unittest.TestCase):
154 DIRECT_CAUSE_ERROR_CODE = """
155 try:
156 x = 1 + 2
157 print(not_defined_here)
158 except Exception as e:
159 x += 55
160 x - 1
161 y = {}
162 raise KeyError('uh') from e
163 """
164
165 EXCEPTION_DURING_HANDLING_CODE = """
166 try:
167 x = 1 + 2
168 print(not_defined_here)
169 except Exception as e:
170 x += 55
171 x - 1
172 y = {}
173 raise KeyError('uh')
174 """
175
176 def test_direct_cause_error(self):
177 if PY3:
178 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
179 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
180
181 def test_exception_during_handling_error(self):
182 if PY3:
183 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
184 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
This diff has been collapsed as it changes many lines, (533 lines changed) Show them Hide them
@@ -39,7 +39,7 b" Give it a shot--you'll love it or you'll hate it."
39 Verbose).
39 Verbose).
40
40
41
41
42 Installation instructions for ColorTB::
42 Installation instructions for VerboseTB::
43
43
44 import sys,ultratb
44 import sys,ultratb
45 sys.excepthook = ultratb.VerboseTB()
45 sys.excepthook = ultratb.VerboseTB()
@@ -73,11 +73,11 b' Inheritance diagram:'
73 """
73 """
74
74
75 #*****************************************************************************
75 #*****************************************************************************
76 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
76 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
77 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
77 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
78 #
78 #
79 # Distributed under the terms of the BSD License. The full license is in
79 # Distributed under the terms of the BSD License. The full license is in
80 # the file COPYING, distributed as part of this software.
80 # the file COPYING, distributed as part of this software.
81 #*****************************************************************************
81 #*****************************************************************************
82
82
83 from __future__ import unicode_literals
83 from __future__ import unicode_literals
@@ -95,14 +95,14 b' import tokenize'
95 import traceback
95 import traceback
96 import types
96 import types
97
97
98 try: # Python 2
98 try: # Python 2
99 generate_tokens = tokenize.generate_tokens
99 generate_tokens = tokenize.generate_tokens
100 except AttributeError: # Python 3
100 except AttributeError: # Python 3
101 generate_tokens = tokenize.tokenize
101 generate_tokens = tokenize.tokenize
102
102
103 # For purposes of monkeypatching inspect to fix a bug in it.
103 # For purposes of monkeypatching inspect to fix a bug in it.
104 from inspect import getsourcefile, getfile, getmodule,\
104 from inspect import getsourcefile, getfile, getmodule, \
105 ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode
105 ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode
106
106
107 # IPython's own modules
107 # IPython's own modules
108 # Modified pdb which doesn't damage IPython's readline handling
108 # Modified pdb which doesn't damage IPython's readline handling
@@ -125,11 +125,11 b' INDENT_SIZE = 8'
125
125
126 # Default color scheme. This is used, for example, by the traceback
126 # Default color scheme. This is used, for example, by the traceback
127 # formatter. When running in an actual IPython instance, the user's rc.colors
127 # formatter. When running in an actual IPython instance, the user's rc.colors
128 # value is used, but havinga module global makes this functionality available
128 # value is used, but having a module global makes this functionality available
129 # to users of ultratb who are NOT running inside ipython.
129 # to users of ultratb who are NOT running inside ipython.
130 DEFAULT_SCHEME = 'NoColor'
130 DEFAULT_SCHEME = 'NoColor'
131
131
132 #---------------------------------------------------------------------------
132 # ---------------------------------------------------------------------------
133 # Code begins
133 # Code begins
134
134
135 # Utility functions
135 # Utility functions
@@ -141,6 +141,7 b' def inspect_error():'
141 error('Internal Python error in the inspect module.\n'
141 error('Internal Python error in the inspect module.\n'
142 'Below is the traceback from this internal error.\n')
142 'Below is the traceback from this internal error.\n')
143
143
144
144 # This function is a monkeypatch we apply to the Python inspect module. We have
145 # This function is a monkeypatch we apply to the Python inspect module. We have
145 # now found when it's needed (see discussion on issue gh-1456), and we have a
146 # now found when it's needed (see discussion on issue gh-1456), and we have a
146 # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if
147 # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if
@@ -212,7 +213,7 b' def findsource(object):'
212 pmatch = pat.match
213 pmatch = pat.match
213 # fperez - fix: sometimes, co_firstlineno can give a number larger than
214 # fperez - fix: sometimes, co_firstlineno can give a number larger than
214 # the length of lines, which causes an error. Safeguard against that.
215 # the length of lines, which causes an error. Safeguard against that.
215 lnum = min(object.co_firstlineno,len(lines))-1
216 lnum = min(object.co_firstlineno, len(lines)) - 1
216 while lnum > 0:
217 while lnum > 0:
217 if pmatch(lines[lnum]): break
218 if pmatch(lines[lnum]): break
218 lnum -= 1
219 lnum -= 1
@@ -220,9 +221,11 b' def findsource(object):'
220 return lines, lnum
221 return lines, lnum
221 raise IOError('could not find code object')
222 raise IOError('could not find code object')
222
223
224
223 # Monkeypatch inspect to apply our bugfix.
225 # Monkeypatch inspect to apply our bugfix.
224 def with_patch_inspect(f):
226 def with_patch_inspect(f):
225 """decorator for monkeypatching inspect.findsource"""
227 """decorator for monkeypatching inspect.findsource"""
228
226 def wrapped(*args, **kwargs):
229 def wrapped(*args, **kwargs):
227 save_findsource = inspect.findsource
230 save_findsource = inspect.findsource
228 inspect.findsource = findsource
231 inspect.findsource = findsource
@@ -230,8 +233,10 b' def with_patch_inspect(f):'
230 return f(*args, **kwargs)
233 return f(*args, **kwargs)
231 finally:
234 finally:
232 inspect.findsource = save_findsource
235 inspect.findsource = save_findsource
236
233 return wrapped
237 return wrapped
234
238
239
235 def fix_frame_records_filenames(records):
240 def fix_frame_records_filenames(records):
236 """Try to fix the filenames in each record from inspect.getinnerframes().
241 """Try to fix the filenames in each record from inspect.getinnerframes().
237
242
@@ -253,11 +258,10 b' def fix_frame_records_filenames(records):'
253
258
254
259
255 @with_patch_inspect
260 @with_patch_inspect
256 def _fixed_getinnerframes(etb, context=1,tb_offset=0):
261 def _fixed_getinnerframes(etb, context=1, tb_offset=0):
257 LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5
262 LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5
258
259 records = fix_frame_records_filenames(inspect.getinnerframes(etb, context))
260
263
264 records = fix_frame_records_filenames(inspect.getinnerframes(etb, context))
261 # If the error is at the console, don't build any context, since it would
265 # If the error is at the console, don't build any context, since it would
262 # otherwise produce 5 blank lines printed out (there is no file at the
266 # otherwise produce 5 blank lines printed out (there is no file at the
263 # console)
267 # console)
@@ -272,9 +276,9 b' def _fixed_getinnerframes(etb, context=1,tb_offset=0):'
272 aux = traceback.extract_tb(etb)
276 aux = traceback.extract_tb(etb)
273 assert len(records) == len(aux)
277 assert len(records) == len(aux)
274 for i, (file, lnum, _, _) in zip(range(len(records)), aux):
278 for i, (file, lnum, _, _) in zip(range(len(records)), aux):
275 maybeStart = lnum-1 - context//2
279 maybeStart = lnum - 1 - context // 2
276 start = max(maybeStart, 0)
280 start = max(maybeStart, 0)
277 end = start + context
281 end = start + context
278 lines = ulinecache.getlines(file)[start:end]
282 lines = ulinecache.getlines(file)[start:end]
279 buf = list(records[i])
283 buf = list(records[i])
280 buf[LNUM_POS] = lnum
284 buf[LNUM_POS] = lnum
@@ -290,7 +294,8 b' def _fixed_getinnerframes(etb, context=1,tb_offset=0):'
290
294
291 _parser = PyColorize.Parser()
295 _parser = PyColorize.Parser()
292
296
293 def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):
297
298 def _format_traceback_lines(lnum, index, lines, Colors, lvals=None, scheme=None):
294 numbers_width = INDENT_SIZE - 1
299 numbers_width = INDENT_SIZE - 1
295 res = []
300 res = []
296 i = lnum - index
301 i = lnum - index
@@ -315,7 +320,7 b' def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):'
315 # This is the line with the error
320 # This is the line with the error
316 pad = numbers_width - len(str(i))
321 pad = numbers_width - len(str(i))
317 if pad >= 3:
322 if pad >= 3:
318 marker = '-'*(pad-3) + '-> '
323 marker = '-' * (pad - 3) + '-> '
319 elif pad == 2:
324 elif pad == 2:
320 marker = '> '
325 marker = '> '
321 elif pad == 1:
326 elif pad == 1:
@@ -323,12 +328,12 b' def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):'
323 else:
328 else:
324 marker = ''
329 marker = ''
325 num = marker + str(i)
330 num = marker + str(i)
326 line = '%s%s%s %s%s' %(Colors.linenoEm, num,
331 line = '%s%s%s %s%s' % (Colors.linenoEm, num,
327 Colors.line, line, Colors.Normal)
332 Colors.line, line, Colors.Normal)
328 else:
333 else:
329 num = '%*s' % (numbers_width,i)
334 num = '%*s' % (numbers_width, i)
330 line = '%s%s%s %s' %(Colors.lineno, num,
335 line = '%s%s%s %s' % (Colors.lineno, num,
331 Colors.Normal, line)
336 Colors.Normal, line)
332
337
333 res.append(line)
338 res.append(line)
334 if lvals and i == lnum:
339 if lvals and i == lnum:
@@ -389,16 +394,16 b' class TBTools(object):'
389
394
390 ostream = property(_get_ostream, _set_ostream)
395 ostream = property(_get_ostream, _set_ostream)
391
396
392 def set_colors(self,*args,**kw):
397 def set_colors(self, *args, **kw):
393 """Shorthand access to the color table scheme selector method."""
398 """Shorthand access to the color table scheme selector method."""
394
399
395 # Set own color table
400 # Set own color table
396 self.color_scheme_table.set_active_scheme(*args,**kw)
401 self.color_scheme_table.set_active_scheme(*args, **kw)
397 # for convenience, set Colors to the active scheme
402 # for convenience, set Colors to the active scheme
398 self.Colors = self.color_scheme_table.active_colors
403 self.Colors = self.color_scheme_table.active_colors
399 # Also set colors of debugger
404 # Also set colors of debugger
400 if hasattr(self,'pdb') and self.pdb is not None:
405 if hasattr(self, 'pdb') and self.pdb is not None:
401 self.pdb.set_colors(*args,**kw)
406 self.pdb.set_colors(*args, **kw)
402
407
403 def color_toggle(self):
408 def color_toggle(self):
404 """Toggle between the currently active color scheme and NoColor."""
409 """Toggle between the currently active color scheme and NoColor."""
@@ -453,7 +458,7 b' class ListTB(TBTools):'
453 Because they are meant to be called without a full traceback (only a
458 Because they are meant to be called without a full traceback (only a
454 list), instances of this class can't call the interactive pdb debugger."""
459 list), instances of this class can't call the interactive pdb debugger."""
455
460
456 def __init__(self,color_scheme = 'NoColor', call_pdb=False, ostream=None):
461 def __init__(self, color_scheme='NoColor', call_pdb=False, ostream=None):
457 TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
462 TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
458 ostream=ostream)
463 ostream=ostream)
459
464
@@ -497,7 +502,7 b' class ListTB(TBTools):'
497 elist = elist[tb_offset:]
502 elist = elist[tb_offset:]
498
503
499 out_list.append('Traceback %s(most recent call last)%s:' %
504 out_list.append('Traceback %s(most recent call last)%s:' %
500 (Colors.normalEm, Colors.Normal) + '\n')
505 (Colors.normalEm, Colors.Normal) + '\n')
501 out_list.extend(self._format_list(elist))
506 out_list.extend(self._format_list(elist))
502 # The exception info should be a single entry in the list.
507 # The exception info should be a single entry in the list.
503 lines = ''.join(self._format_exception_only(etype, value))
508 lines = ''.join(self._format_exception_only(etype, value))
@@ -510,7 +515,7 b' class ListTB(TBTools):'
510 ## out_list.append(lines[-1])
515 ## out_list.append(lines[-1])
511
516
512 # This means it was indenting everything but the last line by a little
517 # This means it was indenting everything but the last line by a little
513 # bit. I've disabled this for now, but if we see ugliness somewhre we
518 # bit. I've disabled this for now, but if we see ugliness somewhere we
514 # can restore it.
519 # can restore it.
515
520
516 return out_list
521 return out_list
@@ -532,25 +537,24 b' class ListTB(TBTools):'
532 list = []
537 list = []
533 for filename, lineno, name, line in extracted_list[:-1]:
538 for filename, lineno, name, line in extracted_list[:-1]:
534 item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \
539 item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \
535 (Colors.filename, filename, Colors.Normal,
540 (Colors.filename, filename, Colors.Normal,
536 Colors.lineno, lineno, Colors.Normal,
541 Colors.lineno, lineno, Colors.Normal,
537 Colors.name, name, Colors.Normal)
542 Colors.name, name, Colors.Normal)
538 if line:
543 if line:
539 item += ' %s\n' % line.strip()
544 item += ' %s\n' % line.strip()
540 list.append(item)
545 list.append(item)
541 # Emphasize the last entry
546 # Emphasize the last entry
542 filename, lineno, name, line = extracted_list[-1]
547 filename, lineno, name, line = extracted_list[-1]
543 item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \
548 item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \
544 (Colors.normalEm,
549 (Colors.normalEm,
545 Colors.filenameEm, filename, Colors.normalEm,
550 Colors.filenameEm, filename, Colors.normalEm,
546 Colors.linenoEm, lineno, Colors.normalEm,
551 Colors.linenoEm, lineno, Colors.normalEm,
547 Colors.nameEm, name, Colors.normalEm,
552 Colors.nameEm, name, Colors.normalEm,
548 Colors.Normal)
553 Colors.Normal)
549 if line:
554 if line:
550 item += '%s %s%s\n' % (Colors.line, line.strip(),
555 item += '%s %s%s\n' % (Colors.line, line.strip(),
551 Colors.Normal)
556 Colors.Normal)
552 list.append(item)
557 list.append(item)
553 #from pprint import pformat; print 'LISTTB', pformat(list) # dbg
554 return list
558 return list
555
559
556 def _format_exception_only(self, etype, value):
560 def _format_exception_only(self, etype, value):
@@ -572,11 +576,10 b' class ListTB(TBTools):'
572 stype = Colors.excName + etype.__name__ + Colors.Normal
576 stype = Colors.excName + etype.__name__ + Colors.Normal
573 if value is None:
577 if value is None:
574 # Not sure if this can still happen in Python 2.6 and above
578 # Not sure if this can still happen in Python 2.6 and above
575 list.append( py3compat.cast_unicode(stype) + '\n')
579 list.append(py3compat.cast_unicode(stype) + '\n')
576 else:
580 else:
577 if issubclass(etype, SyntaxError):
581 if issubclass(etype, SyntaxError):
578 have_filedata = True
582 have_filedata = True
579 #print 'filename is',filename # dbg
580 if not value.filename: value.filename = "<string>"
583 if not value.filename: value.filename = "<string>"
581 if value.lineno:
584 if value.lineno:
582 lineno = value.lineno
585 lineno = value.lineno
@@ -585,9 +588,9 b' class ListTB(TBTools):'
585 lineno = 'unknown'
588 lineno = 'unknown'
586 textline = ''
589 textline = ''
587 list.append('%s File %s"%s"%s, line %s%s%s\n' % \
590 list.append('%s File %s"%s"%s, line %s%s%s\n' % \
588 (Colors.normalEm,
591 (Colors.normalEm,
589 Colors.filenameEm, py3compat.cast_unicode(value.filename), Colors.normalEm,
592 Colors.filenameEm, py3compat.cast_unicode(value.filename), Colors.normalEm,
590 Colors.linenoEm, lineno, Colors.Normal ))
593 Colors.linenoEm, lineno, Colors.Normal ))
591 if textline == '':
594 if textline == '':
592 textline = py3compat.cast_unicode(value.text, "utf-8")
595 textline = py3compat.cast_unicode(value.text, "utf-8")
593
596
@@ -600,13 +603,13 b' class ListTB(TBTools):'
600 Colors.Normal))
603 Colors.Normal))
601 if value.offset is not None:
604 if value.offset is not None:
602 s = ' '
605 s = ' '
603 for c in textline[i:value.offset-1]:
606 for c in textline[i:value.offset - 1]:
604 if c.isspace():
607 if c.isspace():
605 s += c
608 s += c
606 else:
609 else:
607 s += ' '
610 s += ' '
608 list.append('%s%s^%s\n' % (Colors.caret, s,
611 list.append('%s%s^%s\n' % (Colors.caret, s,
609 Colors.Normal) )
612 Colors.Normal))
610
613
611 try:
614 try:
612 s = value.msg
615 s = value.msg
@@ -636,7 +639,6 b' class ListTB(TBTools):'
636 """
639 """
637 return ListTB.structured_traceback(self, etype, value, [])
640 return ListTB.structured_traceback(self, etype, value, [])
638
641
639
640 def show_exception_only(self, etype, evalue):
642 def show_exception_only(self, etype, evalue):
641 """Only print the exception type and message, without a traceback.
643 """Only print the exception type and message, without a traceback.
642
644
@@ -659,6 +661,7 b' class ListTB(TBTools):'
659 except:
661 except:
660 return '<unprintable %s object>' % type(value).__name__
662 return '<unprintable %s object>' % type(value).__name__
661
663
664
662 #----------------------------------------------------------------------------
665 #----------------------------------------------------------------------------
663 class VerboseTB(TBTools):
666 class VerboseTB(TBTools):
664 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
667 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
@@ -668,7 +671,7 b' class VerboseTB(TBTools):'
668 traceback, to be used with alternate interpreters (because their own code
671 traceback, to be used with alternate interpreters (because their own code
669 would appear in the traceback)."""
672 would appear in the traceback)."""
670
673
671 def __init__(self,color_scheme = 'Linux', call_pdb=False, ostream=None,
674 def __init__(self, color_scheme='Linux', call_pdb=False, ostream=None,
672 tb_offset=0, long_header=False, include_vars=True,
675 tb_offset=0, long_header=False, include_vars=True,
673 check_cache=None):
676 check_cache=None):
674 """Specify traceback offset, headers and color scheme.
677 """Specify traceback offset, headers and color scheme.
@@ -691,126 +694,37 b' class VerboseTB(TBTools):'
691 check_cache = linecache.checkcache
694 check_cache = linecache.checkcache
692 self.check_cache = check_cache
695 self.check_cache = check_cache
693
696
694 def structured_traceback(self, etype, evalue, etb, tb_offset=None,
697 def format_records(self, records):
695 context=5):
698 Colors = self.Colors # just a shorthand + quicker name lookup
696 """Return a nice text document describing the traceback."""
699 ColorsNormal = Colors.Normal # used a lot
697
700 col_scheme = self.color_scheme_table.active_scheme_name
698 tb_offset = self.tb_offset if tb_offset is None else tb_offset
701 indent = ' ' * INDENT_SIZE
699
702 em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal)
700 # some locals
703 undefined = '%sundefined%s' % (Colors.em, ColorsNormal)
701 try:
702 etype = etype.__name__
703 except AttributeError:
704 pass
705 Colors = self.Colors # just a shorthand + quicker name lookup
706 ColorsNormal = Colors.Normal # used a lot
707 col_scheme = self.color_scheme_table.active_scheme_name
708 indent = ' '*INDENT_SIZE
709 em_normal = '%s\n%s%s' % (Colors.valEm, indent,ColorsNormal)
710 undefined = '%sundefined%s' % (Colors.em, ColorsNormal)
711 exc = '%s%s%s' % (Colors.excName,etype,ColorsNormal)
712
713 # some internal-use functions
714 def text_repr(value):
715 """Hopefully pretty robust repr equivalent."""
716 # this is pretty horrible but should always return *something*
717 try:
718 return pydoc.text.repr(value)
719 except KeyboardInterrupt:
720 raise
721 except:
722 try:
723 return repr(value)
724 except KeyboardInterrupt:
725 raise
726 except:
727 try:
728 # all still in an except block so we catch
729 # getattr raising
730 name = getattr(value, '__name__', None)
731 if name:
732 # ick, recursion
733 return text_repr(name)
734 klass = getattr(value, '__class__', None)
735 if klass:
736 return '%s instance' % text_repr(klass)
737 except KeyboardInterrupt:
738 raise
739 except:
740 return 'UNRECOVERABLE REPR FAILURE'
741 def eqrepr(value, repr=text_repr): return '=%s' % repr(value)
742 def nullrepr(value, repr=text_repr): return ''
743
744 # meat of the code begins
745 try:
746 etype = etype.__name__
747 except AttributeError:
748 pass
749
750 if self.long_header:
751 # Header with the exception type, python version, and date
752 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
753 date = time.ctime(time.time())
754
755 head = '%s%s%s\n%s%s%s\n%s' % (Colors.topline, '-'*75, ColorsNormal,
756 exc, ' '*(75-len(str(etype))-len(pyver)),
757 pyver, date.rjust(75) )
758 head += "\nA problem occured executing Python code. Here is the sequence of function"\
759 "\ncalls leading up to the error, with the most recent (innermost) call last."
760 else:
761 # Simplified header
762 head = '%s%s%s\n%s%s' % (Colors.topline, '-'*75, ColorsNormal,exc,
763 'Traceback (most recent call last)'.\
764 rjust(75 - len(str(etype)) ) )
765 frames = []
704 frames = []
766 # Flush cache before calling inspect. This helps alleviate some of the
767 # problems with python 2.3's inspect.py.
768 ##self.check_cache()
769 # Drop topmost frames if requested
770 try:
771 # Try the default getinnerframes and Alex's: Alex's fixes some
772 # problems, but it generates empty tracebacks for console errors
773 # (5 blanks lines) where none should be returned.
774 #records = inspect.getinnerframes(etb, context)[tb_offset:]
775 #print 'python records:', records # dbg
776 records = _fixed_getinnerframes(etb, context, tb_offset)
777 #print 'alex records:', records # dbg
778 except:
779
780 # FIXME: I've been getting many crash reports from python 2.3
781 # users, traceable to inspect.py. If I can find a small test-case
782 # to reproduce this, I should either write a better workaround or
783 # file a bug report against inspect (if that's the real problem).
784 # So far, I haven't been able to find an isolated example to
785 # reproduce the problem.
786 inspect_error()
787 traceback.print_exc(file=self.ostream)
788 info('\nUnfortunately, your original traceback can not be constructed.\n')
789 return ''
790
791 # build some color string templates outside these nested loops
705 # build some color string templates outside these nested loops
792 tpl_link = '%s%%s%s' % (Colors.filenameEm,ColorsNormal)
706 tpl_link = '%s%%s%s' % (Colors.filenameEm, ColorsNormal)
793 tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm,
707 tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm,
794 ColorsNormal)
708 ColorsNormal)
795 tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \
709 tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \
796 (Colors.vName, Colors.valEm, ColorsNormal)
710 (Colors.vName, Colors.valEm, ColorsNormal)
797 tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal)
711 tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal)
798 tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal,
712 tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal,
799 Colors.vName, ColorsNormal)
713 Colors.vName, ColorsNormal)
800 tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal)
714 tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal)
801 tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal)
715
802 tpl_line_em = '%s%%s%s %%s%s' % (Colors.linenoEm,Colors.line,
716 tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal)
803 ColorsNormal)
717 tpl_line_em = '%s%%s%s %%s%s' % (Colors.linenoEm, Colors.line,
718 ColorsNormal)
804
719
805 # now, loop over all records printing context and info
806 abspath = os.path.abspath
720 abspath = os.path.abspath
807 for frame, file, lnum, func, lines, index in records:
721 for frame, file, lnum, func, lines, index in records:
808 #print '*** record:',file,lnum,func,lines,index # dbg
722 #print '*** record:',file,lnum,func,lines,index # dbg
809 if not file:
723 if not file:
810 file = '?'
724 file = '?'
811 elif not(file.startswith(str("<")) and file.endswith(str(">"))):
725 elif not (file.startswith(str("<")) and file.endswith(str(">"))):
812 # Guess that filenames like <string> aren't real filenames, so
726 # Guess that filenames like <string> aren't real filenames, so
813 # don't call abspath on them.
727 # don't call abspath on them.
814 try:
728 try:
815 file = abspath(file)
729 file = abspath(file)
816 except OSError:
730 except OSError:
@@ -827,9 +741,9 b' class VerboseTB(TBTools):'
827 # Decide whether to include variable details or not
741 # Decide whether to include variable details or not
828 var_repr = self.include_vars and eqrepr or nullrepr
742 var_repr = self.include_vars and eqrepr or nullrepr
829 try:
743 try:
830 call = tpl_call % (func,inspect.formatargvalues(args,
744 call = tpl_call % (func, inspect.formatargvalues(args,
831 varargs, varkw,
745 varargs, varkw,
832 locals,formatvalue=var_repr))
746 locals, formatvalue=var_repr))
833 except KeyError:
747 except KeyError:
834 # This happens in situations like errors inside generator
748 # This happens in situations like errors inside generator
835 # expressions, where local variables are listed in the
749 # expressions, where local variables are listed in the
@@ -848,12 +762,12 b' class VerboseTB(TBTools):'
848 # will illustrate the error, if this exception catch is
762 # will illustrate the error, if this exception catch is
849 # disabled.
763 # disabled.
850 call = tpl_call_fail % func
764 call = tpl_call_fail % func
851
765
852 # Don't attempt to tokenize binary files.
766 # Don't attempt to tokenize binary files.
853 if file.endswith(('.so', '.pyd', '.dll')):
767 if file.endswith(('.so', '.pyd', '.dll')):
854 frames.append('%s %s\n' % (link,call))
768 frames.append('%s %s\n' % (link, call))
855 continue
769 continue
856 elif file.endswith(('.pyc','.pyo')):
770 elif file.endswith(('.pyc', '.pyo')):
857 # Look up the corresponding source file.
771 # Look up the corresponding source file.
858 file = openpy.source_from_cache(file)
772 file = openpy.source_from_cache(file)
859
773
@@ -867,7 +781,7 b' class VerboseTB(TBTools):'
867 try:
781 try:
868 names = []
782 names = []
869 name_cont = False
783 name_cont = False
870
784
871 for token_type, token, start, end, line in generate_tokens(linereader):
785 for token_type, token, start, end, line in generate_tokens(linereader):
872 # build composite names
786 # build composite names
873 if token_type == tokenize.NAME and token not in keyword.kwlist:
787 if token_type == tokenize.NAME and token not in keyword.kwlist:
@@ -890,9 +804,11 b' class VerboseTB(TBTools):'
890 name_cont = True
804 name_cont = True
891 elif token_type == tokenize.NEWLINE:
805 elif token_type == tokenize.NEWLINE:
892 break
806 break
893
807
894 except (IndexError, UnicodeDecodeError):
808 except (IndexError, UnicodeDecodeError, SyntaxError):
895 # signals exit of tokenizer
809 # signals exit of tokenizer
810 # SyntaxError can occur if the file is not actually Python
811 # - see gh-6300
896 pass
812 pass
897 except tokenize.TokenError as msg:
813 except tokenize.TokenError as msg:
898 _m = ("An unexpected error occurred while tokenizing input\n"
814 _m = ("An unexpected error occurred while tokenizing input\n"
@@ -909,11 +825,11 b' class VerboseTB(TBTools):'
909 lvals = []
825 lvals = []
910 if self.include_vars:
826 if self.include_vars:
911 for name_full in unique_names:
827 for name_full in unique_names:
912 name_base = name_full.split('.',1)[0]
828 name_base = name_full.split('.', 1)[0]
913 if name_base in frame.f_code.co_varnames:
829 if name_base in frame.f_code.co_varnames:
914 if name_base in locals:
830 if name_base in locals:
915 try:
831 try:
916 value = repr(eval(name_full,locals))
832 value = repr(eval(name_full, locals))
917 except:
833 except:
918 value = undefined
834 value = undefined
919 else:
835 else:
@@ -922,69 +838,191 b' class VerboseTB(TBTools):'
922 else:
838 else:
923 if name_base in frame.f_globals:
839 if name_base in frame.f_globals:
924 try:
840 try:
925 value = repr(eval(name_full,frame.f_globals))
841 value = repr(eval(name_full, frame.f_globals))
926 except:
842 except:
927 value = undefined
843 value = undefined
928 else:
844 else:
929 value = undefined
845 value = undefined
930 name = tpl_global_var % name_full
846 name = tpl_global_var % name_full
931 lvals.append(tpl_name_val % (name,value))
847 lvals.append(tpl_name_val % (name, value))
932 if lvals:
848 if lvals:
933 lvals = '%s%s' % (indent,em_normal.join(lvals))
849 lvals = '%s%s' % (indent, em_normal.join(lvals))
934 else:
850 else:
935 lvals = ''
851 lvals = ''
936
852
937 level = '%s %s\n' % (link,call)
853 level = '%s %s\n' % (link, call)
938
854
939 if index is None:
855 if index is None:
940 frames.append(level)
856 frames.append(level)
941 else:
857 else:
942 frames.append('%s%s' % (level,''.join(
858 frames.append('%s%s' % (level, ''.join(
943 _format_traceback_lines(lnum,index,lines,Colors,lvals,
859 _format_traceback_lines(lnum, index, lines, Colors, lvals,
944 col_scheme))))
860 col_scheme))))
945
861
862 return frames
863
864 def prepare_chained_exception_message(self, cause):
865 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
866 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
867
868 if cause:
869 message = [[direct_cause]]
870 else:
871 message = [[exception_during_handling]]
872 return message
873
874 def prepare_header(self, etype, long_version=False):
875 colors = self.Colors # just a shorthand + quicker name lookup
876 colorsnormal = colors.Normal # used a lot
877 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
878 if long_version:
879 # Header with the exception type, python version, and date
880 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
881 date = time.ctime(time.time())
882
883 head = '%s%s%s\n%s%s%s\n%s' % (colors.topline, '-' * 75, colorsnormal,
884 exc, ' ' * (75 - len(str(etype)) - len(pyver)),
885 pyver, date.rjust(75) )
886 head += "\nA problem occurred executing Python code. Here is the sequence of function" \
887 "\ncalls leading up to the error, with the most recent (innermost) call last."
888 else:
889 # Simplified header
890 head = '%s%s' % (exc, 'Traceback (most recent call last)'. \
891 rjust(75 - len(str(etype))) )
892
893 return head
894
895 def format_exception(self, etype, evalue):
896 colors = self.Colors # just a shorthand + quicker name lookup
897 colorsnormal = colors.Normal # used a lot
898 indent = ' ' * INDENT_SIZE
946 # Get (safely) a string form of the exception info
899 # Get (safely) a string form of the exception info
947 try:
900 try:
948 etype_str,evalue_str = map(str,(etype,evalue))
901 etype_str, evalue_str = map(str, (etype, evalue))
949 except:
902 except:
950 # User exception is improperly defined.
903 # User exception is improperly defined.
951 etype,evalue = str,sys.exc_info()[:2]
904 etype, evalue = str, sys.exc_info()[:2]
952 etype_str,evalue_str = map(str,(etype,evalue))
905 etype_str, evalue_str = map(str, (etype, evalue))
953 # ... and format it
906 # ... and format it
954 exception = ['%s%s%s: %s' % (Colors.excName, etype_str,
907 exception = ['%s%s%s: %s' % (colors.excName, etype_str,
955 ColorsNormal, py3compat.cast_unicode(evalue_str))]
908 colorsnormal, py3compat.cast_unicode(evalue_str))]
909
956 if (not py3compat.PY3) and type(evalue) is types.InstanceType:
910 if (not py3compat.PY3) and type(evalue) is types.InstanceType:
957 try:
911 try:
958 names = [w for w in dir(evalue) if isinstance(w, py3compat.string_types)]
912 names = [w for w in dir(evalue) if isinstance(w, py3compat.string_types)]
959 except:
913 except:
960 # Every now and then, an object with funny inernals blows up
914 # Every now and then, an object with funny internals blows up
961 # when dir() is called on it. We do the best we can to report
915 # when dir() is called on it. We do the best we can to report
962 # the problem and continue
916 # the problem and continue
963 _m = '%sException reporting error (object with broken dir())%s:'
917 _m = '%sException reporting error (object with broken dir())%s:'
964 exception.append(_m % (Colors.excName,ColorsNormal))
918 exception.append(_m % (colors.excName, colorsnormal))
965 etype_str,evalue_str = map(str,sys.exc_info()[:2])
919 etype_str, evalue_str = map(str, sys.exc_info()[:2])
966 exception.append('%s%s%s: %s' % (Colors.excName,etype_str,
920 exception.append('%s%s%s: %s' % (colors.excName, etype_str,
967 ColorsNormal, py3compat.cast_unicode(evalue_str)))
921 colorsnormal, py3compat.cast_unicode(evalue_str)))
968 names = []
922 names = []
969 for name in names:
923 for name in names:
970 value = text_repr(getattr(evalue, name))
924 value = text_repr(getattr(evalue, name))
971 exception.append('\n%s%s = %s' % (indent, name, value))
925 exception.append('\n%s%s = %s' % (indent, name, value))
972
926
973 # vds: >>
927 return exception
928
929 def format_exception_as_a_whole(self, etype, evalue, etb, number_of_lines_of_context, tb_offset):
930 # some locals
931 try:
932 etype = etype.__name__
933 except AttributeError:
934 pass
935
936 tb_offset = self.tb_offset if tb_offset is None else tb_offset
937 head = self.prepare_header(etype, self.long_header)
938 records = self.get_records(etb, number_of_lines_of_context, tb_offset)
939
940 frames = self.format_records(records)
941 if records is None:
942 return ""
943
944 formatted_exception = self.format_exception(etype, evalue)
974 if records:
945 if records:
975 filepath, lnum = records[-1][1:3]
946 filepath, lnum = records[-1][1:3]
976 #print "file:", str(file), "linenb", str(lnum) # dbg
947 filepath = os.path.abspath(filepath)
977 filepath = os.path.abspath(filepath)
948 ipinst = get_ipython()
978 ipinst = get_ipython()
949 if ipinst is not None:
979 if ipinst is not None:
950 ipinst.hooks.synchronize_with_editor(filepath, lnum, 0)
980 ipinst.hooks.synchronize_with_editor(filepath, lnum, 0)
951
981 # vds: <<
952 return [[head] + frames + [''.join(formatted_exception[0])]]
982
953
983 # return all our info assembled as a single string
954 def get_records(self, etb, number_of_lines_of_context, tb_offset):
984 # return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) )
955 try:
985 return [head] + frames + [''.join(exception[0])]
956 # Try the default getinnerframes and Alex's: Alex's fixes some
986
957 # problems, but it generates empty tracebacks for console errors
987 def debugger(self,force=False):
958 # (5 blanks lines) where none should be returned.
959 return _fixed_getinnerframes(etb, number_of_lines_of_context, tb_offset)
960 except:
961 # FIXME: I've been getting many crash reports from python 2.3
962 # users, traceable to inspect.py. If I can find a small test-case
963 # to reproduce this, I should either write a better workaround or
964 # file a bug report against inspect (if that's the real problem).
965 # So far, I haven't been able to find an isolated example to
966 # reproduce the problem.
967 inspect_error()
968 traceback.print_exc(file=self.ostream)
969 info('\nUnfortunately, your original traceback can not be constructed.\n')
970 return None
971
972 def get_parts_of_chained_exception(self, evalue):
973 def get_chained_exception(exception_value):
974 cause = getattr(exception_value, '__cause__', None)
975 if cause:
976 return cause
977 return getattr(exception_value, '__context__', None)
978
979 chained_evalue = get_chained_exception(evalue)
980
981 if chained_evalue:
982 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
983
984 def structured_traceback(self, etype, evalue, etb, tb_offset=None,
985 number_of_lines_of_context=5):
986 """Return a nice text document describing the traceback."""
987
988 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
989 tb_offset)
990
991 colors = self.Colors # just a shorthand + quicker name lookup
992 colorsnormal = colors.Normal # used a lot
993 head = '%s%s%s' % (colors.topline, '-' * 75, colorsnormal)
994 structured_traceback_parts = [head]
995 if py3compat.PY3:
996 chained_exceptions_tb_offset = 0
997 lines_of_context = 3
998 formatted_exceptions = formatted_exception
999 exception = self.get_parts_of_chained_exception(evalue)
1000 if exception:
1001 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1002 etype, evalue, etb = exception
1003 else:
1004 evalue = None
1005 while evalue:
1006 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1007 chained_exceptions_tb_offset)
1008 exception = self.get_parts_of_chained_exception(evalue)
1009
1010 if exception:
1011 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1012 etype, evalue, etb = exception
1013 else:
1014 evalue = None
1015
1016 # we want to see exceptions in a reversed order:
1017 # the first exception should be on top
1018 for formatted_exception in reversed(formatted_exceptions):
1019 structured_traceback_parts += formatted_exception
1020 else:
1021 structured_traceback_parts += formatted_exception[0]
1022
1023 return structured_traceback_parts
1024
1025 def debugger(self, force=False):
988 """Call up the pdb debugger if desired, always clean up the tb
1026 """Call up the pdb debugger if desired, always clean up the tb
989 reference.
1027 reference.
990
1028
@@ -1014,7 +1052,7 b' class VerboseTB(TBTools):'
1014 with display_trap:
1052 with display_trap:
1015 self.pdb.reset()
1053 self.pdb.reset()
1016 # Find the right frame so we don't pop up inside ipython itself
1054 # Find the right frame so we don't pop up inside ipython itself
1017 if hasattr(self,'tb') and self.tb is not None:
1055 if hasattr(self, 'tb') and self.tb is not None:
1018 etb = self.tb
1056 etb = self.tb
1019 else:
1057 else:
1020 etb = self.tb = sys.last_traceback
1058 etb = self.tb = sys.last_traceback
@@ -1025,7 +1063,7 b' class VerboseTB(TBTools):'
1025 self.pdb.botframe = etb.tb_frame
1063 self.pdb.botframe = etb.tb_frame
1026 self.pdb.interaction(self.tb.tb_frame, self.tb)
1064 self.pdb.interaction(self.tb.tb_frame, self.tb)
1027
1065
1028 if hasattr(self,'tb'):
1066 if hasattr(self, 'tb'):
1029 del self.tb
1067 del self.tb
1030
1068
1031 def handler(self, info=None):
1069 def handler(self, info=None):
@@ -1050,6 +1088,7 b' class VerboseTB(TBTools):'
1050 except KeyboardInterrupt:
1088 except KeyboardInterrupt:
1051 print("\nKeyboardInterrupt")
1089 print("\nKeyboardInterrupt")
1052
1090
1091
1053 #----------------------------------------------------------------------------
1092 #----------------------------------------------------------------------------
1054 class FormattedTB(VerboseTB, ListTB):
1093 class FormattedTB(VerboseTB, ListTB):
1055 """Subclass ListTB but allow calling with a traceback.
1094 """Subclass ListTB but allow calling with a traceback.
@@ -1069,7 +1108,7 b' class FormattedTB(VerboseTB, ListTB):'
1069 check_cache=None):
1108 check_cache=None):
1070
1109
1071 # NEVER change the order of this list. Put new modes at the end:
1110 # NEVER change the order of this list. Put new modes at the end:
1072 self.valid_modes = ['Plain','Context','Verbose']
1111 self.valid_modes = ['Plain', 'Context', 'Verbose']
1073 self.verbose_modes = self.valid_modes[1:3]
1112 self.verbose_modes = self.valid_modes[1:3]
1074
1113
1075 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1114 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
@@ -1083,19 +1122,19 b' class FormattedTB(VerboseTB, ListTB):'
1083 # set_mode also sets the tb_join_char attribute
1122 # set_mode also sets the tb_join_char attribute
1084 self.set_mode(mode)
1123 self.set_mode(mode)
1085
1124
1086 def _extract_tb(self,tb):
1125 def _extract_tb(self, tb):
1087 if tb:
1126 if tb:
1088 return traceback.extract_tb(tb)
1127 return traceback.extract_tb(tb)
1089 else:
1128 else:
1090 return None
1129 return None
1091
1130
1092 def structured_traceback(self, etype, value, tb, tb_offset=None, context=5):
1131 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1093 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1132 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1094 mode = self.mode
1133 mode = self.mode
1095 if mode in self.verbose_modes:
1134 if mode in self.verbose_modes:
1096 # Verbose modes need a full traceback
1135 # Verbose modes need a full traceback
1097 return VerboseTB.structured_traceback(
1136 return VerboseTB.structured_traceback(
1098 self, etype, value, tb, tb_offset, context
1137 self, etype, value, tb, tb_offset, number_of_lines_of_context
1099 )
1138 )
1100 else:
1139 else:
1101 # We must check the source cache because otherwise we can print
1140 # We must check the source cache because otherwise we can print
@@ -1104,7 +1143,7 b' class FormattedTB(VerboseTB, ListTB):'
1104 # Now we can extract and format the exception
1143 # Now we can extract and format the exception
1105 elist = self._extract_tb(tb)
1144 elist = self._extract_tb(tb)
1106 return ListTB.structured_traceback(
1145 return ListTB.structured_traceback(
1107 self, etype, value, elist, tb_offset, context
1146 self, etype, value, elist, tb_offset, number_of_lines_of_context
1108 )
1147 )
1109
1148
1110 def stb2text(self, stb):
1149 def stb2text(self, stb):
@@ -1112,18 +1151,18 b' class FormattedTB(VerboseTB, ListTB):'
1112 return self.tb_join_char.join(stb)
1151 return self.tb_join_char.join(stb)
1113
1152
1114
1153
1115 def set_mode(self,mode=None):
1154 def set_mode(self, mode=None):
1116 """Switch to the desired mode.
1155 """Switch to the desired mode.
1117
1156
1118 If mode is not specified, cycles through the available modes."""
1157 If mode is not specified, cycles through the available modes."""
1119
1158
1120 if not mode:
1159 if not mode:
1121 new_idx = ( self.valid_modes.index(self.mode) + 1 ) % \
1160 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1122 len(self.valid_modes)
1161 len(self.valid_modes)
1123 self.mode = self.valid_modes[new_idx]
1162 self.mode = self.valid_modes[new_idx]
1124 elif mode not in self.valid_modes:
1163 elif mode not in self.valid_modes:
1125 raise ValueError('Unrecognized mode in FormattedTB: <'+mode+'>\n'
1164 raise ValueError('Unrecognized mode in FormattedTB: <' + mode + '>\n'
1126 'Valid modes: '+str(self.valid_modes))
1165 'Valid modes: ' + str(self.valid_modes))
1127 else:
1166 else:
1128 self.mode = mode
1167 self.mode = mode
1129 # include variable details only in 'Verbose' mode
1168 # include variable details only in 'Verbose' mode
@@ -1131,7 +1170,7 b' class FormattedTB(VerboseTB, ListTB):'
1131 # Set the join character for generating text tracebacks
1170 # Set the join character for generating text tracebacks
1132 self.tb_join_char = self._join_chars[self.mode]
1171 self.tb_join_char = self._join_chars[self.mode]
1133
1172
1134 # some convenient shorcuts
1173 # some convenient shortcuts
1135 def plain(self):
1174 def plain(self):
1136 self.set_mode(self.valid_modes[0])
1175 self.set_mode(self.valid_modes[0])
1137
1176
@@ -1141,6 +1180,7 b' class FormattedTB(VerboseTB, ListTB):'
1141 def verbose(self):
1180 def verbose(self):
1142 self.set_mode(self.valid_modes[2])
1181 self.set_mode(self.valid_modes[2])
1143
1182
1183
1144 #----------------------------------------------------------------------------
1184 #----------------------------------------------------------------------------
1145 class AutoFormattedTB(FormattedTB):
1185 class AutoFormattedTB(FormattedTB):
1146 """A traceback printer which can be called on the fly.
1186 """A traceback printer which can be called on the fly.
@@ -1156,8 +1196,8 b' class AutoFormattedTB(FormattedTB):'
1156 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1196 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1157 """
1197 """
1158
1198
1159 def __call__(self,etype=None,evalue=None,etb=None,
1199 def __call__(self, etype=None, evalue=None, etb=None,
1160 out=None,tb_offset=None):
1200 out=None, tb_offset=None):
1161 """Print out a formatted exception traceback.
1201 """Print out a formatted exception traceback.
1162
1202
1163 Optional arguments:
1203 Optional arguments:
@@ -1167,7 +1207,6 b' class AutoFormattedTB(FormattedTB):'
1167 per-call basis (this overrides temporarily the instance's tb_offset
1207 per-call basis (this overrides temporarily the instance's tb_offset
1168 given at initialization time. """
1208 given at initialization time. """
1169
1209
1170
1171 if out is None:
1210 if out is None:
1172 out = self.ostream
1211 out = self.ostream
1173 out.flush()
1212 out.flush()
@@ -1182,33 +1221,36 b' class AutoFormattedTB(FormattedTB):'
1182 print("\nKeyboardInterrupt")
1221 print("\nKeyboardInterrupt")
1183
1222
1184 def structured_traceback(self, etype=None, value=None, tb=None,
1223 def structured_traceback(self, etype=None, value=None, tb=None,
1185 tb_offset=None, context=5):
1224 tb_offset=None, number_of_lines_of_context=5):
1186 if etype is None:
1225 if etype is None:
1187 etype,value,tb = sys.exc_info()
1226 etype, value, tb = sys.exc_info()
1188 self.tb = tb
1227 self.tb = tb
1189 return FormattedTB.structured_traceback(
1228 return FormattedTB.structured_traceback(
1190 self, etype, value, tb, tb_offset, context)
1229 self, etype, value, tb, tb_offset, number_of_lines_of_context)
1230
1191
1231
1192 #---------------------------------------------------------------------------
1232 #---------------------------------------------------------------------------
1193
1233
1194 # A simple class to preserve Nathan's original functionality.
1234 # A simple class to preserve Nathan's original functionality.
1195 class ColorTB(FormattedTB):
1235 class ColorTB(FormattedTB):
1196 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1236 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1197 def __init__(self,color_scheme='Linux',call_pdb=0):
1237
1198 FormattedTB.__init__(self,color_scheme=color_scheme,
1238 def __init__(self, color_scheme='Linux', call_pdb=0):
1239 FormattedTB.__init__(self, color_scheme=color_scheme,
1199 call_pdb=call_pdb)
1240 call_pdb=call_pdb)
1200
1241
1201
1242
1202 class SyntaxTB(ListTB):
1243 class SyntaxTB(ListTB):
1203 """Extension which holds some state: the last exception value"""
1244 """Extension which holds some state: the last exception value"""
1204
1245
1205 def __init__(self,color_scheme = 'NoColor'):
1246 def __init__(self, color_scheme='NoColor'):
1206 ListTB.__init__(self,color_scheme)
1247 ListTB.__init__(self, color_scheme)
1207 self.last_syntax_error = None
1248 self.last_syntax_error = None
1208
1249
1209 def __call__(self, etype, value, elist):
1250 def __call__(self, etype, value, elist):
1210 self.last_syntax_error = value
1251 self.last_syntax_error = value
1211 ListTB.__call__(self,etype,value,elist)
1252
1253 ListTB.__call__(self, etype, value, elist)
1212
1254
1213 def structured_traceback(self, etype, value, elist, tb_offset=None,
1255 def structured_traceback(self, etype, value, elist, tb_offset=None,
1214 context=5):
1256 context=5):
@@ -1223,7 +1265,7 b' class SyntaxTB(ListTB):'
1223 if newtext:
1265 if newtext:
1224 value.text = newtext
1266 value.text = newtext
1225 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1267 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1226 tb_offset=tb_offset, context=context)
1268 tb_offset=tb_offset, context=context)
1227
1269
1228 def clear_err_state(self):
1270 def clear_err_state(self):
1229 """Return the current error state and clear it"""
1271 """Return the current error state and clear it"""
@@ -1236,7 +1278,46 b' class SyntaxTB(ListTB):'
1236 return ''.join(stb)
1278 return ''.join(stb)
1237
1279
1238
1280
1281 # some internal-use functions
1282 def text_repr(value):
1283 """Hopefully pretty robust repr equivalent."""
1284 # this is pretty horrible but should always return *something*
1285 try:
1286 return pydoc.text.repr(value)
1287 except KeyboardInterrupt:
1288 raise
1289 except:
1290 try:
1291 return repr(value)
1292 except KeyboardInterrupt:
1293 raise
1294 except:
1295 try:
1296 # all still in an except block so we catch
1297 # getattr raising
1298 name = getattr(value, '__name__', None)
1299 if name:
1300 # ick, recursion
1301 return text_repr(name)
1302 klass = getattr(value, '__class__', None)
1303 if klass:
1304 return '%s instance' % text_repr(klass)
1305 except KeyboardInterrupt:
1306 raise
1307 except:
1308 return 'UNRECOVERABLE REPR FAILURE'
1309
1310
1311 def eqrepr(value, repr=text_repr):
1312 return '=%s' % repr(value)
1313
1314
1315 def nullrepr(value, repr=text_repr):
1316 return ''
1317
1318
1239 #----------------------------------------------------------------------------
1319 #----------------------------------------------------------------------------
1320
1240 # module testing (minimal)
1321 # module testing (minimal)
1241 if __name__ == "__main__":
1322 if __name__ == "__main__":
1242 def spam(c, d_e):
1323 def spam(c, d_e):
@@ -136,7 +136,7 b' def extract_zip(fd, dest):'
136 os.rename(os.path.join(parent, topdir), dest)
136 os.rename(os.path.join(parent, topdir), dest)
137
137
138
138
139 def install_mathjax(tag='v2.2', dest=default_dest, replace=False, file=None, extractor=extract_tar):
139 def install_mathjax(tag='2.4.0', dest=default_dest, replace=False, file=None, extractor=extract_tar):
140 """Download and/or install MathJax for offline use.
140 """Download and/or install MathJax for offline use.
141
141
142 This will install mathjax to the nbextensions dir in your IPYTHONDIR.
142 This will install mathjax to the nbextensions dir in your IPYTHONDIR.
@@ -150,8 +150,8 b" def install_mathjax(tag='v2.2', dest=default_dest, replace=False, file=None, ext"
150 Whether to remove and replace an existing install.
150 Whether to remove and replace an existing install.
151 dest : str [IPYTHONDIR/nbextensions/mathjax]
151 dest : str [IPYTHONDIR/nbextensions/mathjax]
152 Where to install mathjax
152 Where to install mathjax
153 tag : str ['v2.2']
153 tag : str ['2.4.0']
154 Which tag to download. Default is 'v2.2', the current stable release,
154 Which tag to download. Default is '2.4.0', the current stable release,
155 but alternatives include 'v1.1a' and 'master'.
155 but alternatives include 'v1.1a' and 'master'.
156 file : file like object [ defualt to content of https://github.com/mathjax/MathJax/tarball/#{tag}]
156 file : file like object [ defualt to content of https://github.com/mathjax/MathJax/tarball/#{tag}]
157 File handle from which to untar/unzip/... mathjax
157 File handle from which to untar/unzip/... mathjax
@@ -80,6 +80,7 b' try:'
80 import traceback
80 import traceback
81 import signal
81 import signal
82 import codecs
82 import codecs
83 import stat
83 except ImportError: # pragma: no cover
84 except ImportError: # pragma: no cover
84 err = sys.exc_info()[1]
85 err = sys.exc_info()[1]
85 raise ImportError(str(err) + '''
86 raise ImportError(str(err) + '''
@@ -87,7 +88,7 b' except ImportError: # pragma: no cover'
87 A critical module was not found. Probably this operating system does not
88 A critical module was not found. Probably this operating system does not
88 support it. Pexpect is intended for UNIX-like operating systems.''')
89 support it. Pexpect is intended for UNIX-like operating systems.''')
89
90
90 __version__ = '3.2'
91 __version__ = '3.3'
91 __revision__ = ''
92 __revision__ = ''
92 __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
93 __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
93 'which', 'split_command_line', '__version__', '__revision__']
94 'which', 'split_command_line', '__version__', '__revision__']
@@ -284,6 +285,7 b' class spawn(object):'
284 def _chr(c):
285 def _chr(c):
285 return bytes([c])
286 return bytes([c])
286 linesep = os.linesep.encode('ascii')
287 linesep = os.linesep.encode('ascii')
288 crlf = '\r\n'.encode('ascii')
287
289
288 @staticmethod
290 @staticmethod
289 def write_to_stdout(b):
291 def write_to_stdout(b):
@@ -296,13 +298,14 b' class spawn(object):'
296 allowed_string_types = (basestring,) # analysis:ignore
298 allowed_string_types = (basestring,) # analysis:ignore
297 _chr = staticmethod(chr)
299 _chr = staticmethod(chr)
298 linesep = os.linesep
300 linesep = os.linesep
301 crlf = '\r\n'
299 write_to_stdout = sys.stdout.write
302 write_to_stdout = sys.stdout.write
300
303
301 encoding = None
304 encoding = None
302
305
303 def __init__(self, command, args=[], timeout=30, maxread=2000,
306 def __init__(self, command, args=[], timeout=30, maxread=2000,
304 searchwindowsize=None, logfile=None, cwd=None, env=None,
307 searchwindowsize=None, logfile=None, cwd=None, env=None,
305 ignore_sighup=True):
308 ignore_sighup=True, echo=True):
306
309
307 '''This is the constructor. The command parameter may be a string that
310 '''This is the constructor. The command parameter may be a string that
308 includes a command and any arguments to the command. For example::
311 includes a command and any arguments to the command. For example::
@@ -415,7 +418,16 b' class spawn(object):'
415 signalstatus will store the signal value and exitstatus will be None.
418 signalstatus will store the signal value and exitstatus will be None.
416 If you need more detail you can also read the self.status member which
419 If you need more detail you can also read the self.status member which
417 stores the status returned by os.waitpid. You can interpret this using
420 stores the status returned by os.waitpid. You can interpret this using
418 os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. '''
421 os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG.
422
423 The echo attribute may be set to False to disable echoing of input.
424 As a pseudo-terminal, all input echoed by the "keyboard" (send()
425 or sendline()) will be repeated to output. For many cases, it is
426 not desirable to have echo enabled, and it may be later disabled
427 using setecho(False) followed by waitnoecho(). However, for some
428 platforms such as Solaris, this is not possible, and should be
429 disabled immediately on spawn.
430 '''
419
431
420 self.STDIN_FILENO = pty.STDIN_FILENO
432 self.STDIN_FILENO = pty.STDIN_FILENO
421 self.STDOUT_FILENO = pty.STDOUT_FILENO
433 self.STDOUT_FILENO = pty.STDOUT_FILENO
@@ -437,7 +449,7 b' class spawn(object):'
437 self.status = None
449 self.status = None
438 self.flag_eof = False
450 self.flag_eof = False
439 self.pid = None
451 self.pid = None
440 # the chile filedescriptor is initially closed
452 # the child file descriptor is initially closed
441 self.child_fd = -1
453 self.child_fd = -1
442 self.timeout = timeout
454 self.timeout = timeout
443 self.delimiter = EOF
455 self.delimiter = EOF
@@ -466,16 +478,30 b' class spawn(object):'
466 self.closed = True
478 self.closed = True
467 self.cwd = cwd
479 self.cwd = cwd
468 self.env = env
480 self.env = env
481 self.echo = echo
469 self.ignore_sighup = ignore_sighup
482 self.ignore_sighup = ignore_sighup
483 _platform = sys.platform.lower()
470 # This flags if we are running on irix
484 # This flags if we are running on irix
471 self.__irix_hack = (sys.platform.lower().find('irix') >= 0)
485 self.__irix_hack = _platform.startswith('irix')
472 # Solaris uses internal __fork_pty(). All others use pty.fork().
486 # Solaris uses internal __fork_pty(). All others use pty.fork().
473 if ((sys.platform.lower().find('solaris') >= 0)
487 self.use_native_pty_fork = not (
474 or (sys.platform.lower().find('sunos5') >= 0)):
488 _platform.startswith('solaris') or
475 self.use_native_pty_fork = False
489 _platform.startswith('sunos'))
476 else:
490 # inherit EOF and INTR definitions from controlling process.
477 self.use_native_pty_fork = True
491 try:
478
492 from termios import VEOF, VINTR
493 fd = sys.__stdin__.fileno()
494 self._INTR = ord(termios.tcgetattr(fd)[6][VINTR])
495 self._EOF = ord(termios.tcgetattr(fd)[6][VEOF])
496 except (ImportError, OSError, IOError, termios.error):
497 # unless the controlling process is also not a terminal,
498 # such as cron(1). Fall-back to using CEOF and CINTR.
499 try:
500 from termios import CEOF, CINTR
501 (self._INTR, self._EOF) = (CINTR, CEOF)
502 except ImportError:
503 # ^C, ^D
504 (self._INTR, self._EOF) = (3, 4)
479 # Support subclasses that do not use command or args.
505 # Support subclasses that do not use command or args.
480 if command is None:
506 if command is None:
481 self.command = None
507 self.command = None
@@ -599,33 +625,39 b' class spawn(object):'
599 if self.use_native_pty_fork:
625 if self.use_native_pty_fork:
600 try:
626 try:
601 self.pid, self.child_fd = pty.fork()
627 self.pid, self.child_fd = pty.fork()
602 except OSError:
628 except OSError: # pragma: no cover
603 err = sys.exc_info()[1]
629 err = sys.exc_info()[1]
604 raise ExceptionPexpect('pty.fork() failed: ' + str(err))
630 raise ExceptionPexpect('pty.fork() failed: ' + str(err))
605 else:
631 else:
606 # Use internal __fork_pty
632 # Use internal __fork_pty
607 self.pid, self.child_fd = self.__fork_pty()
633 self.pid, self.child_fd = self.__fork_pty()
608
634
609 if self.pid == 0:
635 # Some platforms must call setwinsize() and setecho() from the
636 # child process, and others from the master process. We do both,
637 # allowing IOError for either.
638
639 if self.pid == pty.CHILD:
610 # Child
640 # Child
641 self.child_fd = self.STDIN_FILENO
642
643 # set default window size of 24 rows by 80 columns
611 try:
644 try:
612 # used by setwinsize()
613 self.child_fd = sys.stdout.fileno()
614 self.setwinsize(24, 80)
645 self.setwinsize(24, 80)
615 # which exception, shouldnt' we catch explicitly .. ?
646 except IOError as err:
616 except:
647 if err.args[0] not in (errno.EINVAL, errno.ENOTTY):
617 # Some platforms do not like setwinsize (Cygwin).
648 raise
618 # This will cause problem when running applications that
649
619 # are very picky about window size.
650 # disable echo if spawn argument echo was unset
620 # This is a serious limitation, but not a show stopper.
651 if not self.echo:
621 pass
652 try:
653 self.setecho(self.echo)
654 except (IOError, termios.error) as err:
655 if err.args[0] not in (errno.EINVAL, errno.ENOTTY):
656 raise
657
622 # Do not allow child to inherit open file descriptors from parent.
658 # Do not allow child to inherit open file descriptors from parent.
623 max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
659 max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
624 for i in range(3, max_fd):
660 os.closerange(3, max_fd)
625 try:
626 os.close(i)
627 except OSError:
628 pass
629
661
630 if self.ignore_sighup:
662 if self.ignore_sighup:
631 signal.signal(signal.SIGHUP, signal.SIG_IGN)
663 signal.signal(signal.SIGHUP, signal.SIG_IGN)
@@ -638,6 +670,13 b' class spawn(object):'
638 os.execvpe(self.command, self.args, self.env)
670 os.execvpe(self.command, self.args, self.env)
639
671
640 # Parent
672 # Parent
673 try:
674 self.setwinsize(24, 80)
675 except IOError as err:
676 if err.args[0] not in (errno.EINVAL, errno.ENOTTY):
677 raise
678
679
641 self.terminated = False
680 self.terminated = False
642 self.closed = False
681 self.closed = False
643
682
@@ -660,19 +699,15 b' class spawn(object):'
660 raise ExceptionPexpect("Could not open with os.openpty().")
699 raise ExceptionPexpect("Could not open with os.openpty().")
661
700
662 pid = os.fork()
701 pid = os.fork()
663 if pid < 0:
702 if pid == pty.CHILD:
664 raise ExceptionPexpect("Failed os.fork().")
665 elif pid == 0:
666 # Child.
703 # Child.
667 os.close(parent_fd)
704 os.close(parent_fd)
668 self.__pty_make_controlling_tty(child_fd)
705 self.__pty_make_controlling_tty(child_fd)
669
706
670 os.dup2(child_fd, 0)
707 os.dup2(child_fd, self.STDIN_FILENO)
671 os.dup2(child_fd, 1)
708 os.dup2(child_fd, self.STDOUT_FILENO)
672 os.dup2(child_fd, 2)
709 os.dup2(child_fd, self.STDERR_FILENO)
673
710
674 if child_fd > 2:
675 os.close(child_fd)
676 else:
711 else:
677 # Parent.
712 # Parent.
678 os.close(child_fd)
713 os.close(child_fd)
@@ -686,44 +721,36 b' class spawn(object):'
686
721
687 child_name = os.ttyname(tty_fd)
722 child_name = os.ttyname(tty_fd)
688
723
689 # Disconnect from controlling tty. Harmless if not already connected.
724 # Disconnect from controlling tty, if any. Raises OSError of ENXIO
725 # if there was no controlling tty to begin with, such as when
726 # executed by a cron(1) job.
690 try:
727 try:
691 fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
728 fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
692 if fd >= 0:
729 os.close(fd)
693 os.close(fd)
730 except OSError as err:
694 # which exception, shouldnt' we catch explicitly .. ?
731 if err.errno != errno.ENXIO:
695 except:
732 raise
696 # Already disconnected. This happens if running inside cron.
697 pass
698
733
699 os.setsid()
734 os.setsid()
700
735
701 # Verify we are disconnected from controlling tty
736 # Verify we are disconnected from controlling tty by attempting to open
702 # by attempting to open it again.
737 # it again. We expect that OSError of ENXIO should always be raised.
703 try:
738 try:
704 fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
739 fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
705 if fd >= 0:
740 os.close(fd)
706 os.close(fd)
741 raise ExceptionPexpect("OSError of errno.ENXIO should be raised.")
707 raise ExceptionPexpect('Failed to disconnect from ' +
742 except OSError as err:
708 'controlling tty. It is still possible to open /dev/tty.')
743 if err.errno != errno.ENXIO:
709 # which exception, shouldnt' we catch explicitly .. ?
744 raise
710 except:
711 # Good! We are disconnected from a controlling tty.
712 pass
713
745
714 # Verify we can open child pty.
746 # Verify we can open child pty.
715 fd = os.open(child_name, os.O_RDWR)
747 fd = os.open(child_name, os.O_RDWR)
716 if fd < 0:
748 os.close(fd)
717 raise ExceptionPexpect("Could not open child pty, " + child_name)
718 else:
719 os.close(fd)
720
749
721 # Verify we now have a controlling tty.
750 # Verify we now have a controlling tty.
722 fd = os.open("/dev/tty", os.O_WRONLY)
751 fd = os.open("/dev/tty", os.O_WRONLY)
723 if fd < 0:
752 os.close(fd)
724 raise ExceptionPexpect("Could not open controlling tty, /dev/tty")
753
725 else:
726 os.close(fd)
727
754
728 def fileno(self):
755 def fileno(self):
729 '''This returns the file descriptor of the pty for the child.
756 '''This returns the file descriptor of the pty for the child.
@@ -757,7 +784,12 b' class spawn(object):'
757
784
758 def isatty(self):
785 def isatty(self):
759 '''This returns True if the file descriptor is open and connected to a
786 '''This returns True if the file descriptor is open and connected to a
760 tty(-like) device, else False. '''
787 tty(-like) device, else False.
788
789 On SVR4-style platforms implementing streams, such as SunOS and HP-UX,
790 the child pty may not appear as a terminal device. This means
791 methods such as setecho(), setwinsize(), getwinsize() may raise an
792 IOError. '''
761
793
762 return os.isatty(self.child_fd)
794 return os.isatty(self.child_fd)
763
795
@@ -794,12 +826,20 b' class spawn(object):'
794 def getecho(self):
826 def getecho(self):
795 '''This returns the terminal echo mode. This returns True if echo is
827 '''This returns the terminal echo mode. This returns True if echo is
796 on or False if echo is off. Child applications that are expecting you
828 on or False if echo is off. Child applications that are expecting you
797 to enter a password often set ECHO False. See waitnoecho(). '''
829 to enter a password often set ECHO False. See waitnoecho().
798
830
799 attr = termios.tcgetattr(self.child_fd)
831 Not supported on platforms where ``isatty()`` returns False. '''
800 if attr[3] & termios.ECHO:
832
801 return True
833 try:
802 return False
834 attr = termios.tcgetattr(self.child_fd)
835 except termios.error as err:
836 errmsg = 'getecho() may not be called on this platform'
837 if err.args[0] == errno.EINVAL:
838 raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
839 raise
840
841 self.echo = bool(attr[3] & termios.ECHO)
842 return self.echo
803
843
804 def setecho(self, state):
844 def setecho(self, state):
805 '''This sets the terminal echo mode on or off. Note that anything the
845 '''This sets the terminal echo mode on or off. Note that anything the
@@ -829,18 +869,35 b' class spawn(object):'
829 p.expect(['1234'])
869 p.expect(['1234'])
830 p.expect(['abcd'])
870 p.expect(['abcd'])
831 p.expect(['wxyz'])
871 p.expect(['wxyz'])
872
873
874 Not supported on platforms where ``isatty()`` returns False.
832 '''
875 '''
833
876
834 self.child_fd
877 errmsg = 'setecho() may not be called on this platform'
835 attr = termios.tcgetattr(self.child_fd)
878
879 try:
880 attr = termios.tcgetattr(self.child_fd)
881 except termios.error as err:
882 if err.args[0] == errno.EINVAL:
883 raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
884 raise
885
836 if state:
886 if state:
837 attr[3] = attr[3] | termios.ECHO
887 attr[3] = attr[3] | termios.ECHO
838 else:
888 else:
839 attr[3] = attr[3] & ~termios.ECHO
889 attr[3] = attr[3] & ~termios.ECHO
840 # I tried TCSADRAIN and TCSAFLUSH, but
890
841 # these were inconsistent and blocked on some platforms.
891 try:
842 # TCSADRAIN would probably be ideal if it worked.
892 # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and
843 termios.tcsetattr(self.child_fd, termios.TCSANOW, attr)
893 # blocked on some platforms. TCSADRAIN would probably be ideal.
894 termios.tcsetattr(self.child_fd, termios.TCSANOW, attr)
895 except IOError as err:
896 if err.args[0] == errno.EINVAL:
897 raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
898 raise
899
900 self.echo = state
844
901
845 def _log(self, s, direction):
902 def _log(self, s, direction):
846 if self.logfile is not None:
903 if self.logfile is not None:
@@ -913,12 +970,14 b' class spawn(object):'
913 if self.child_fd in r:
970 if self.child_fd in r:
914 try:
971 try:
915 s = os.read(self.child_fd, size)
972 s = os.read(self.child_fd, size)
916 except OSError:
973 except OSError as err:
917 # Linux does this
974 if err.args[0] == errno.EIO:
918 self.flag_eof = True
975 # Linux-style EOF
919 raise EOF('End Of File (EOF). Exception style platform.')
976 self.flag_eof = True
977 raise EOF('End Of File (EOF). Exception style platform.')
978 raise
920 if s == b'':
979 if s == b'':
921 # BSD style
980 # BSD-style EOF
922 self.flag_eof = True
981 self.flag_eof = True
923 raise EOF('End Of File (EOF). Empty string style platform.')
982 raise EOF('End Of File (EOF). Empty string style platform.')
924
983
@@ -926,7 +985,7 b' class spawn(object):'
926 self._log(s, 'read')
985 self._log(s, 'read')
927 return s
986 return s
928
987
929 raise ExceptionPexpect('Reached an unexpected state.')
988 raise ExceptionPexpect('Reached an unexpected state.') # pragma: no cover
930
989
931 def read(self, size=-1):
990 def read(self, size=-1):
932 '''This reads at most "size" bytes from the file (less if the read hits
991 '''This reads at most "size" bytes from the file (less if the read hits
@@ -972,9 +1031,9 b' class spawn(object):'
972 if size == 0:
1031 if size == 0:
973 return self.string_type()
1032 return self.string_type()
974 # delimiter default is EOF
1033 # delimiter default is EOF
975 index = self.expect([b'\r\n', self.delimiter])
1034 index = self.expect([self.crlf, self.delimiter])
976 if index == 0:
1035 if index == 0:
977 return self.before + b'\r\n'
1036 return self.before + self.crlf
978 else:
1037 else:
979 return self.before
1038 return self.before
980
1039
@@ -1075,40 +1134,14 b' class spawn(object):'
1075 It is the responsibility of the caller to ensure the eof is sent at the
1134 It is the responsibility of the caller to ensure the eof is sent at the
1076 beginning of a line. '''
1135 beginning of a line. '''
1077
1136
1078 ### Hmmm... how do I send an EOF?
1137 self.send(self._chr(self._EOF))
1079 ###C if ((m = write(pty, *buf, p - *buf)) < 0)
1080 ###C return (errno == EWOULDBLOCK) ? n : -1;
1081 #fd = sys.stdin.fileno()
1082 #old = termios.tcgetattr(fd) # remember current state
1083 #attr = termios.tcgetattr(fd)
1084 #attr[3] = attr[3] | termios.ICANON # ICANON must be set to see EOF
1085 #try: # use try/finally to ensure state gets restored
1086 # termios.tcsetattr(fd, termios.TCSADRAIN, attr)
1087 # if hasattr(termios, 'CEOF'):
1088 # os.write(self.child_fd, '%c' % termios.CEOF)
1089 # else:
1090 # # Silly platform does not define CEOF so assume CTRL-D
1091 # os.write(self.child_fd, '%c' % 4)
1092 #finally: # restore state
1093 # termios.tcsetattr(fd, termios.TCSADRAIN, old)
1094 if hasattr(termios, 'VEOF'):
1095 char = ord(termios.tcgetattr(self.child_fd)[6][termios.VEOF])
1096 else:
1097 # platform does not define VEOF so assume CTRL-D
1098 char = 4
1099 self.send(self._chr(char))
1100
1138
1101 def sendintr(self):
1139 def sendintr(self):
1102
1140
1103 '''This sends a SIGINT to the child. It does not require
1141 '''This sends a SIGINT to the child. It does not require
1104 the SIGINT to be the first character on a line. '''
1142 the SIGINT to be the first character on a line. '''
1105
1143
1106 if hasattr(termios, 'VINTR'):
1144 self.send(self._chr(self._INTR))
1107 char = ord(termios.tcgetattr(self.child_fd)[6][termios.VINTR])
1108 else:
1109 # platform does not define VINTR so assume CTRL-C
1110 char = 3
1111 self.send(self._chr(char))
1112
1145
1113 def eof(self):
1146 def eof(self):
1114
1147
@@ -1181,7 +1214,7 b' class spawn(object):'
1181 self.exitstatus = None
1214 self.exitstatus = None
1182 self.signalstatus = os.WTERMSIG(status)
1215 self.signalstatus = os.WTERMSIG(status)
1183 self.terminated = True
1216 self.terminated = True
1184 elif os.WIFSTOPPED(status):
1217 elif os.WIFSTOPPED(status): # pragma: no cover
1185 # You can't call wait() on a child process in the stopped state.
1218 # You can't call wait() on a child process in the stopped state.
1186 raise ExceptionPexpect('Called wait() on a stopped child ' +
1219 raise ExceptionPexpect('Called wait() on a stopped child ' +
1187 'process. This is not supported. Is some other ' +
1220 'process. This is not supported. Is some other ' +
@@ -1201,7 +1234,7 b' class spawn(object):'
1201
1234
1202 if self.flag_eof:
1235 if self.flag_eof:
1203 # This is for Linux, which requires the blocking form
1236 # This is for Linux, which requires the blocking form
1204 # of waitpid to # get status of a defunct process.
1237 # of waitpid to get the status of a defunct process.
1205 # This is super-lame. The flag_eof would have been set
1238 # This is super-lame. The flag_eof would have been set
1206 # in read_nonblocking(), so this should be safe.
1239 # in read_nonblocking(), so this should be safe.
1207 waitpid_options = 0
1240 waitpid_options = 0
@@ -1229,7 +1262,7 b' class spawn(object):'
1229 try:
1262 try:
1230 ### os.WNOHANG) # Solaris!
1263 ### os.WNOHANG) # Solaris!
1231 pid, status = os.waitpid(self.pid, waitpid_options)
1264 pid, status = os.waitpid(self.pid, waitpid_options)
1232 except OSError as e:
1265 except OSError as e: # pragma: no cover
1233 # This should never happen...
1266 # This should never happen...
1234 if e.errno == errno.ECHILD:
1267 if e.errno == errno.ECHILD:
1235 raise ExceptionPexpect('isalive() encountered condition ' +
1268 raise ExceptionPexpect('isalive() encountered condition ' +
@@ -1643,10 +1676,14 b' class spawn(object):'
1643 if self.child_fd in r:
1676 if self.child_fd in r:
1644 try:
1677 try:
1645 data = self.__interact_read(self.child_fd)
1678 data = self.__interact_read(self.child_fd)
1646 except OSError as e:
1679 except OSError as err:
1647 # The subprocess may have closed before we get to reading it
1680 if err.args[0] == errno.EIO:
1648 if e.errno != errno.EIO:
1681 # Linux-style EOF
1649 raise
1682 break
1683 raise
1684 if data == b'':
1685 # BSD-style EOF
1686 break
1650 if output_filter:
1687 if output_filter:
1651 data = output_filter(data)
1688 data = output_filter(data)
1652 if self.logfile is not None:
1689 if self.logfile is not None:
@@ -1695,7 +1732,7 b' class spawn(object):'
1695 ##############################################################################
1732 ##############################################################################
1696 # The following methods are no longer supported or allowed.
1733 # The following methods are no longer supported or allowed.
1697
1734
1698 def setmaxread(self, maxread):
1735 def setmaxread(self, maxread): # pragma: no cover
1699
1736
1700 '''This method is no longer supported or allowed. I don't like getters
1737 '''This method is no longer supported or allowed. I don't like getters
1701 and setters without a good reason. '''
1738 and setters without a good reason. '''
@@ -1704,7 +1741,7 b' class spawn(object):'
1704 'or allowed. Just assign a value to the ' +
1741 'or allowed. Just assign a value to the ' +
1705 'maxread member variable.')
1742 'maxread member variable.')
1706
1743
1707 def setlog(self, fileobject):
1744 def setlog(self, fileobject): # pragma: no cover
1708
1745
1709 '''This method is no longer supported or allowed.
1746 '''This method is no longer supported or allowed.
1710 '''
1747 '''
@@ -1732,11 +1769,13 b' class spawnu(spawn):'
1732 allowed_string_types = (str, )
1769 allowed_string_types = (str, )
1733 _chr = staticmethod(chr)
1770 _chr = staticmethod(chr)
1734 linesep = os.linesep
1771 linesep = os.linesep
1772 crlf = '\r\n'
1735 else:
1773 else:
1736 string_type = unicode
1774 string_type = unicode
1737 allowed_string_types = (unicode, )
1775 allowed_string_types = (unicode, )
1738 _chr = staticmethod(unichr)
1776 _chr = staticmethod(unichr)
1739 linesep = os.linesep.decode('ascii')
1777 linesep = os.linesep.decode('ascii')
1778 crlf = '\r\n'.decode('ascii')
1740 # This can handle unicode in both Python 2 and 3
1779 # This can handle unicode in both Python 2 and 3
1741 write_to_stdout = sys.stdout.write
1780 write_to_stdout = sys.stdout.write
1742
1781
@@ -1959,16 +1998,56 b' class searcher_re(object):'
1959 return best_index
1998 return best_index
1960
1999
1961
2000
1962 def which(filename):
2001 def is_executable_file(path):
2002 """Checks that path is an executable regular file (or a symlink to a file).
2003
2004 This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``, but
2005 on some platforms :func:`os.access` gives us the wrong answer, so this
2006 checks permission bits directly.
2007 """
2008 # follow symlinks,
2009 fpath = os.path.realpath(path)
1963
2010
2011 # return False for non-files (directories, fifo, etc.)
2012 if not os.path.isfile(fpath):
2013 return False
2014
2015 # On Solaris, etc., "If the process has appropriate privileges, an
2016 # implementation may indicate success for X_OK even if none of the
2017 # execute file permission bits are set."
2018 #
2019 # For this reason, it is necessary to explicitly check st_mode
2020
2021 # get file mode using os.stat, and check if `other',
2022 # that is anybody, may read and execute.
2023 mode = os.stat(fpath).st_mode
2024 if mode & stat.S_IROTH and mode & stat.S_IXOTH:
2025 return True
2026
2027 # get current user's group ids, and check if `group',
2028 # when matching ours, may read and execute.
2029 user_gids = os.getgroups() + [os.getgid()]
2030 if (os.stat(fpath).st_gid in user_gids and
2031 mode & stat.S_IRGRP and mode & stat.S_IXGRP):
2032 return True
2033
2034 # finally, if file owner matches our effective userid,
2035 # check if `user', may read and execute.
2036 user_gids = os.getgroups() + [os.getgid()]
2037 if (os.stat(fpath).st_uid == os.geteuid() and
2038 mode & stat.S_IRUSR and mode & stat.S_IXUSR):
2039 return True
2040
2041 return False
2042
2043 def which(filename):
1964 '''This takes a given filename; tries to find it in the environment path;
2044 '''This takes a given filename; tries to find it in the environment path;
1965 then checks if it is executable. This returns the full path to the filename
2045 then checks if it is executable. This returns the full path to the filename
1966 if found and executable. Otherwise this returns None.'''
2046 if found and executable. Otherwise this returns None.'''
1967
2047
1968 # Special case where filename contains an explicit path.
2048 # Special case where filename contains an explicit path.
1969 if os.path.dirname(filename) != '':
2049 if os.path.dirname(filename) != '' and is_executable_file(filename):
1970 if os.access(filename, os.X_OK):
2050 return filename
1971 return filename
1972 if 'PATH' not in os.environ or os.environ['PATH'] == '':
2051 if 'PATH' not in os.environ or os.environ['PATH'] == '':
1973 p = os.defpath
2052 p = os.defpath
1974 else:
2053 else:
@@ -1976,7 +2055,7 b' def which(filename):'
1976 pathlist = p.split(os.pathsep)
2055 pathlist = p.split(os.pathsep)
1977 for path in pathlist:
2056 for path in pathlist:
1978 ff = os.path.join(path, filename)
2057 ff = os.path.join(path, filename)
1979 if os.access(ff, os.X_OK):
2058 if is_executable_file(ff):
1980 return ff
2059 return ff
1981 return None
2060 return None
1982
2061
@@ -2041,4 +2120,4 b' def split_command_line(command_line):'
2041 arg_list.append(arg)
2120 arg_list.append(arg)
2042 return arg_list
2121 return arg_list
2043
2122
2044 # vi:set sr et ts=4 sw=4 ft=python :
2123 # vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent :
@@ -13,19 +13,18 b' Developers of the IPython Notebook will need to install the following tools:'
13
13
14 We are moving to a model where our JavaScript dependencies are managed using
14 We are moving to a model where our JavaScript dependencies are managed using
15 [bower](http://bower.io/). These packages are installed in `static/components`
15 [bower](http://bower.io/). These packages are installed in `static/components`
16 and committed into our git repo. Our dependencies are described in the file
16 and committed into a separate git repo [ipython/ipython-components](ipython/ipython-components).
17 Our dependencies are described in the file
17 `static/components/bower.json`. To update our bower packages, run `fab update`
18 `static/components/bower.json`. To update our bower packages, run `fab update`
18 in this directory.
19 in this directory.
19
20
20 Because CodeMirror does not use proper semantic versioning for its GitHub tags,
21 we maintain our own fork of CodeMirror that is used with bower. This fork should
22 track the upstream CodeMirror exactly; the only difference is that we are adding
23 semantic versioned tags to our repo.
24
25 ## less
21 ## less
26
22
27 If you edit our `.less` files you will need to run the less compiler to build
23 If you edit our `.less` files you will need to run the less compiler to build
28 our minified css files. This can be done by running `fab css` from this directory.
24 our minified css files. This can be done by running `fab css` from this directory,
25 or `python setup.py css` from the root of the repository.
26 If you are working frequently with `.less` files please consider installing git hooks that
27 rebuild the css files and corresponding maps in `${RepoRoot}/git-hooks/install-hooks.sh`.
29
28
30 ## JavaScript Documentation
29 ## JavaScript Documentation
31
30
@@ -1,21 +1,7 b''
1 """Base Tornado handlers for the notebook.
1 """Base Tornado handlers for the notebook server."""
2
3 Authors:
4
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
19
5
20 import functools
6 import functools
21 import json
7 import json
@@ -41,7 +27,7 b' except ImportError:'
41 from IPython.config import Application
27 from IPython.config import Application
42 from IPython.utils.path import filefind
28 from IPython.utils.path import filefind
43 from IPython.utils.py3compat import string_types
29 from IPython.utils.py3compat import string_types
44 from IPython.html.utils import is_hidden
30 from IPython.html.utils import is_hidden, url_path_join, url_escape
45
31
46 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
47 # Top-level handlers
33 # Top-level handlers
@@ -53,6 +39,10 b' class AuthenticatedHandler(web.RequestHandler):'
53
39
54 def set_default_headers(self):
40 def set_default_headers(self):
55 headers = self.settings.get('headers', {})
41 headers = self.settings.get('headers', {})
42
43 if "X-Frame-Options" not in headers:
44 headers["X-Frame-Options"] = "SAMEORIGIN"
45
56 for header_name,value in headers.items() :
46 for header_name,value in headers.items() :
57 try:
47 try:
58 self.set_header(header_name, value)
48 self.set_header(header_name, value)
@@ -137,6 +127,10 b' class IPythonHandler(AuthenticatedHandler):'
137 @property
127 @property
138 def base_url(self):
128 def base_url(self):
139 return self.settings.get('base_url', '/')
129 return self.settings.get('base_url', '/')
130
131 @property
132 def ws_url(self):
133 return self.settings.get('websocket_url', '')
140
134
141 #---------------------------------------------------------------
135 #---------------------------------------------------------------
142 # Manager objects
136 # Manager objects
@@ -147,8 +141,8 b' class IPythonHandler(AuthenticatedHandler):'
147 return self.settings['kernel_manager']
141 return self.settings['kernel_manager']
148
142
149 @property
143 @property
150 def notebook_manager(self):
144 def contents_manager(self):
151 return self.settings['notebook_manager']
145 return self.settings['contents_manager']
152
146
153 @property
147 @property
154 def cluster_manager(self):
148 def cluster_manager(self):
@@ -162,9 +156,47 b' class IPythonHandler(AuthenticatedHandler):'
162 def kernel_spec_manager(self):
156 def kernel_spec_manager(self):
163 return self.settings['kernel_spec_manager']
157 return self.settings['kernel_spec_manager']
164
158
159 #---------------------------------------------------------------
160 # CORS
161 #---------------------------------------------------------------
162
165 @property
163 @property
166 def project_dir(self):
164 def allow_origin(self):
167 return self.notebook_manager.notebook_dir
165 """Normal Access-Control-Allow-Origin"""
166 return self.settings.get('allow_origin', '')
167
168 @property
169 def allow_origin_pat(self):
170 """Regular expression version of allow_origin"""
171 return self.settings.get('allow_origin_pat', None)
172
173 @property
174 def allow_credentials(self):
175 """Whether to set Access-Control-Allow-Credentials"""
176 return self.settings.get('allow_credentials', False)
177
178 def set_default_headers(self):
179 """Add CORS headers, if defined"""
180 super(IPythonHandler, self).set_default_headers()
181 if self.allow_origin:
182 self.set_header("Access-Control-Allow-Origin", self.allow_origin)
183 elif self.allow_origin_pat:
184 origin = self.get_origin()
185 if origin and self.allow_origin_pat.match(origin):
186 self.set_header("Access-Control-Allow-Origin", origin)
187 if self.allow_credentials:
188 self.set_header("Access-Control-Allow-Credentials", 'true')
189
190 def get_origin(self):
191 # Handle WebSocket Origin naming convention differences
192 # The difference between version 8 and 13 is that in 8 the
193 # client sends a "Sec-Websocket-Origin" header and in 13 it's
194 # simply "Origin".
195 if "Origin" in self.request.headers:
196 origin = self.request.headers.get("Origin")
197 else:
198 origin = self.request.headers.get("Sec-Websocket-Origin", None)
199 return origin
168
200
169 #---------------------------------------------------------------
201 #---------------------------------------------------------------
170 # template rendering
202 # template rendering
@@ -183,6 +215,7 b' class IPythonHandler(AuthenticatedHandler):'
183 def template_namespace(self):
215 def template_namespace(self):
184 return dict(
216 return dict(
185 base_url=self.base_url,
217 base_url=self.base_url,
218 ws_url=self.ws_url,
186 logged_in=self.logged_in,
219 logged_in=self.logged_in,
187 login_available=self.login_available,
220 login_available=self.login_available,
188 static_url=self.static_url,
221 static_url=self.static_url,
@@ -202,12 +235,13 b' class IPythonHandler(AuthenticatedHandler):'
202 raise web.HTTPError(400, u'Invalid JSON in body of request')
235 raise web.HTTPError(400, u'Invalid JSON in body of request')
203 return model
236 return model
204
237
205 def get_error_html(self, status_code, **kwargs):
238 def write_error(self, status_code, **kwargs):
206 """render custom error pages"""
239 """render custom error pages"""
207 exception = kwargs.get('exception')
240 exc_info = kwargs.get('exc_info')
208 message = ''
241 message = ''
209 status_message = responses.get(status_code, 'Unknown HTTP Error')
242 status_message = responses.get(status_code, 'Unknown HTTP Error')
210 if exception:
243 if exc_info:
244 exception = exc_info[1]
211 # get the custom message, if defined
245 # get the custom message, if defined
212 try:
246 try:
213 message = exception.log_message % exception.args
247 message = exception.log_message % exception.args
@@ -227,13 +261,16 b' class IPythonHandler(AuthenticatedHandler):'
227 exception=exception,
261 exception=exception,
228 )
262 )
229
263
264 self.set_header('Content-Type', 'text/html')
230 # render the template
265 # render the template
231 try:
266 try:
232 html = self.render_template('%s.html' % status_code, **ns)
267 html = self.render_template('%s.html' % status_code, **ns)
233 except TemplateNotFound:
268 except TemplateNotFound:
234 self.log.debug("No template for %d", status_code)
269 self.log.debug("No template for %d", status_code)
235 html = self.render_template('error.html', **ns)
270 html = self.render_template('error.html', **ns)
236 return html
271
272 self.write(html)
273
237
274
238
275
239 class Template404(IPythonHandler):
276 class Template404(IPythonHandler):
@@ -372,6 +409,37 b' class TrailingSlashHandler(web.RequestHandler):'
372 def get(self):
409 def get(self):
373 self.redirect(self.request.uri.rstrip('/'))
410 self.redirect(self.request.uri.rstrip('/'))
374
411
412
413 class FilesRedirectHandler(IPythonHandler):
414 """Handler for redirecting relative URLs to the /files/ handler"""
415 def get(self, path=''):
416 cm = self.contents_manager
417 if cm.path_exists(path):
418 # it's a *directory*, redirect to /tree
419 url = url_path_join(self.base_url, 'tree', path)
420 else:
421 orig_path = path
422 # otherwise, redirect to /files
423 parts = path.split('/')
424 path = '/'.join(parts[:-1])
425 name = parts[-1]
426
427 if not cm.file_exists(name=name, path=path) and 'files' in parts:
428 # redirect without files/ iff it would 404
429 # this preserves pre-2.0-style 'files/' links
430 self.log.warn("Deprecated files/ URL: %s", orig_path)
431 parts.remove('files')
432 path = '/'.join(parts[:-1])
433
434 if not cm.file_exists(name=name, path=path):
435 raise web.HTTPError(404)
436
437 url = url_path_join(self.base_url, 'files', path, name)
438 url = url_escape(url)
439 self.log.debug("Redirecting %s to %s", self.request.path, url)
440 self.redirect(url)
441
442
375 #-----------------------------------------------------------------------------
443 #-----------------------------------------------------------------------------
376 # URL pattern fragments for re-use
444 # URL pattern fragments for re-use
377 #-----------------------------------------------------------------------------
445 #-----------------------------------------------------------------------------
@@ -379,6 +447,8 b' class TrailingSlashHandler(web.RequestHandler):'
379 path_regex = r"(?P<path>(?:/.*)*)"
447 path_regex = r"(?P<path>(?:/.*)*)"
380 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
448 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
381 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
449 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
450 file_name_regex = r"(?P<name>[^/]+)"
451 file_path_regex = "%s/%s" % (path_regex, file_name_regex)
382
452
383 #-----------------------------------------------------------------------------
453 #-----------------------------------------------------------------------------
384 # URL to handler mappings
454 # URL to handler mappings
@@ -15,6 +15,9 b' try:'
15 except ImportError:
15 except ImportError:
16 from Cookie import SimpleCookie # Py 2
16 from Cookie import SimpleCookie # Py 2
17 import logging
17 import logging
18
19 import tornado
20 from tornado import ioloop
18 from tornado import web
21 from tornado import web
19 from tornado import websocket
22 from tornado import websocket
20
23
@@ -26,29 +29,36 b' from .handlers import IPythonHandler'
26
29
27
30
28 class ZMQStreamHandler(websocket.WebSocketHandler):
31 class ZMQStreamHandler(websocket.WebSocketHandler):
29
32
30 def same_origin(self):
33 def check_origin(self, origin):
31 """Check to see that origin and host match in the headers."""
34 """Check Origin == Host or Access-Control-Allow-Origin.
32
35
33 # The difference between version 8 and 13 is that in 8 the
36 Tornado >= 4 calls this method automatically, raising 403 if it returns False.
34 # client sends a "Sec-Websocket-Origin" header and in 13 it's
37 We call it explicitly in `open` on Tornado < 4.
35 # simply "Origin".
38 """
36 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"):
39 if self.allow_origin == '*':
37 origin_header = self.request.headers.get("Sec-Websocket-Origin")
40 return True
38 else:
39 origin_header = self.request.headers.get("Origin")
40
41
41 host = self.request.headers.get("Host")
42 host = self.request.headers.get("Host")
42
43
43 # If no header is provided, assume we can't verify origin
44 # If no header is provided, assume we can't verify origin
44 if(origin_header is None or host is None):
45 if(origin is None or host is None):
46 return False
47
48 host_origin = "{0}://{1}".format(self.request.protocol, host)
49
50 # OK if origin matches host
51 if origin == host_origin:
52 return True
53
54 # Check CORS headers
55 if self.allow_origin:
56 return self.allow_origin == origin
57 elif self.allow_origin_pat:
58 return bool(self.allow_origin_pat.match(origin))
59 else:
60 # No CORS headers deny the request
45 return False
61 return False
46
47 parsed_origin = urlparse(origin_header)
48 origin = parsed_origin.netloc
49
50 # Check to see that origin matches host directly, including ports
51 return origin == host
52
62
53 def clear_cookie(self, *args, **kwargs):
63 def clear_cookie(self, *args, **kwargs):
54 """meaningless for websockets"""
64 """meaningless for websockets"""
@@ -94,19 +104,41 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
94 """
104 """
95 return True
105 return True
96
106
107 # ping interval for keeping websockets alive (30 seconds)
108 WS_PING_INTERVAL = 30000
97
109
98 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
110 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
111 ping_callback = None
112
113 def set_default_headers(self):
114 """Undo the set_default_headers in IPythonHandler
115
116 which doesn't make sense for websockets
117 """
118 pass
99
119
100 def open(self, kernel_id):
120 def open(self, kernel_id):
101 self.kernel_id = cast_unicode(kernel_id, 'ascii')
121 self.kernel_id = cast_unicode(kernel_id, 'ascii')
102 # Check to see that origin matches host directly, including ports
122 # Check to see that origin matches host directly, including ports
103 if not self.same_origin():
123 # Tornado 4 already does CORS checking
104 self.log.warn("Cross Origin WebSocket Attempt.")
124 if tornado.version_info[0] < 4:
105 raise web.HTTPError(404)
125 if not self.check_origin(self.get_origin()):
126 self.log.warn("Cross Origin WebSocket Attempt from %s", self.get_origin())
127 raise web.HTTPError(403)
106
128
107 self.session = Session(config=self.config)
129 self.session = Session(config=self.config)
108 self.save_on_message = self.on_message
130 self.save_on_message = self.on_message
109 self.on_message = self.on_first_message
131 self.on_message = self.on_first_message
132 self.ping_callback = ioloop.PeriodicCallback(self.send_ping, WS_PING_INTERVAL)
133 self.ping_callback.start()
134
135 def send_ping(self):
136 """send a ping to keep the websocket alive"""
137 if self.stream.closed() and self.ping_callback is not None:
138 self.ping_callback.stop()
139 return
140
141 self.ping(b'')
110
142
111 def _inject_cookie_message(self, msg):
143 def _inject_cookie_message(self, msg):
112 """Inject the first message, which is the document cookie,
144 """Inject the first message, which is the document cookie,
@@ -8,31 +8,65 b' from subprocess import check_output'
8
8
9 pjoin = os.path.join
9 pjoin = os.path.join
10 static_dir = 'static'
10 static_dir = 'static'
11 components_dir = os.path.join(static_dir, 'components')
11 components_dir = pjoin(static_dir, 'components')
12 here = os.path.dirname(__file__)
12
13
13 min_less_version = '1.4.0'
14 min_less_version = '1.7.0'
14 max_less_version = '1.5.0' # exclusive
15 max_less_version = '1.8.0' # exclusive
15
16
16 def css(minify=True, verbose=False):
17 def _need_css_update():
18 """Does less need to run?"""
19
20 static_path = pjoin(here, static_dir)
21 css_targets = [
22 pjoin(static_path, 'style', '%s.min.css' % name)
23 for name in ('style', 'ipython')
24 ]
25 css_maps = [t + '.map' for t in css_targets]
26 targets = css_targets + css_maps
27 if not all(os.path.exists(t) for t in targets):
28 # some generated files don't exist
29 return True
30 earliest_target = sorted(os.stat(t).st_mtime for t in targets)[0]
31
32 # check if any .less files are newer than the generated targets
33 for (dirpath, dirnames, filenames) in os.walk(static_path):
34 for f in filenames:
35 if f.endswith('.less'):
36 path = pjoin(static_path, dirpath, f)
37 timestamp = os.stat(path).st_mtime
38 if timestamp > earliest_target:
39 return True
40
41 return False
42
43 def css(minify=False, verbose=False, force=False):
17 """generate the css from less files"""
44 """generate the css from less files"""
45 minify = _to_bool(minify)
46 verbose = _to_bool(verbose)
47 force = _to_bool(force)
48 # minify implies force because it's not the default behavior
49 if not force and not minify and not _need_css_update():
50 print("css up-to-date")
51 return
52
18 for name in ('style', 'ipython'):
53 for name in ('style', 'ipython'):
19 source = pjoin('style', "%s.less" % name)
54 source = pjoin('style', "%s.less" % name)
20 target = pjoin('style', "%s.min.css" % name)
55 target = pjoin('style', "%s.min.css" % name)
21 _compile_less(source, target, minify, verbose)
56 sourcemap = pjoin('style', "%s.min.css.map" % name)
57 _compile_less(source, target, sourcemap, minify, verbose)
22
58
23 def _to_bool(b):
59 def _to_bool(b):
24 if not b in ['True', 'False', True, False]:
60 if not b in ['True', 'False', True, False]:
25 abort('boolean expected, got: %s' % b)
61 abort('boolean expected, got: %s' % b)
26 return (b in ['True', True])
62 return (b in ['True', True])
27
63
28 def _compile_less(source, target, minify=True, verbose=False):
64 def _compile_less(source, target, sourcemap, minify=True, verbose=False):
29 """Compile a less file by source and target relative to static_dir"""
65 """Compile a less file by source and target relative to static_dir"""
30 minify = _to_bool(minify)
31 verbose = _to_bool(verbose)
32 min_flag = '-x' if minify is True else ''
66 min_flag = '-x' if minify is True else ''
33 ver_flag = '--verbose' if verbose is True else ''
67 ver_flag = '--verbose' if verbose is True else ''
34
68
35 # pin less to 1.4
69 # pin less to version number from above
36 try:
70 try:
37 out = check_output(['lessc', '--version'])
71 out = check_output(['lessc', '--version'])
38 except OSError as err:
72 except OSError as err:
@@ -45,6 +79,7 b' def _compile_less(source, target, minify=True, verbose=False):'
45 if V(less_version) >= V(max_less_version):
79 if V(less_version) >= V(max_less_version):
46 raise ValueError("lessc too new: %s >= %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, max_less_version))
80 raise ValueError("lessc too new: %s >= %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, max_less_version))
47
81
82 static_path = pjoin(here, static_dir)
48 with lcd(static_dir):
83 with lcd(static_dir):
49 local('lessc {min_flag} {ver_flag} {source} {target}'.format(**locals()))
84 local('lessc {min_flag} {ver_flag} --source-map={sourcemap} --source-map-basepath={static_path} --source-map-rootpath="../" {source} {target}'.format(**locals()))
50
85
@@ -1,10 +1,18 b''
1 """Tornado handlers for nbconvert."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
1 import io
6 import io
2 import os
7 import os
3 import zipfile
8 import zipfile
4
9
5 from tornado import web
10 from tornado import web
6
11
7 from ..base.handlers import IPythonHandler, notebook_path_regex
12 from ..base.handlers import (
13 IPythonHandler, FilesRedirectHandler,
14 notebook_path_regex, path_regex,
15 )
8 from IPython.nbformat.current import to_notebook_json
16 from IPython.nbformat.current import to_notebook_json
9
17
10 from IPython.utils.py3compat import cast_bytes
18 from IPython.utils.py3compat import cast_bytes
@@ -73,7 +81,7 b' class NbconvertFileHandler(IPythonHandler):'
73 exporter = get_exporter(format, config=self.config, log=self.log)
81 exporter = get_exporter(format, config=self.config, log=self.log)
74
82
75 path = path.strip('/')
83 path = path.strip('/')
76 model = self.notebook_manager.get_notebook(name=name, path=path)
84 model = self.contents_manager.get_model(name=name, path=path)
77
85
78 self.set_header('Last-Modified', model['last_modified'])
86 self.set_header('Last-Modified', model['last_modified'])
79
87
@@ -123,6 +131,7 b' class NbconvertPostHandler(IPythonHandler):'
123
131
124 self.finish(output)
132 self.finish(output)
125
133
134
126 #-----------------------------------------------------------------------------
135 #-----------------------------------------------------------------------------
127 # URL to handler mappings
136 # URL to handler mappings
128 #-----------------------------------------------------------------------------
137 #-----------------------------------------------------------------------------
@@ -134,4 +143,5 b' default_handlers = ['
134 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
143 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
135 NbconvertFileHandler),
144 NbconvertFileHandler),
136 (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
145 (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
146 (r"/nbconvert/html%s" % path_regex, FilesRedirectHandler),
137 ]
147 ]
@@ -106,7 +106,7 b' class APITest(NotebookTestBase):'
106
106
107 @onlyif_cmds_exist('pandoc')
107 @onlyif_cmds_exist('pandoc')
108 def test_from_post(self):
108 def test_from_post(self):
109 nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
109 nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
110 nbmodel = requests.get(nbmodel_url).json()
110 nbmodel = requests.get(nbmodel_url).json()
111
111
112 r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
112 r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
@@ -121,7 +121,7 b' class APITest(NotebookTestBase):'
121
121
122 @onlyif_cmds_exist('pandoc')
122 @onlyif_cmds_exist('pandoc')
123 def test_from_post_zip(self):
123 def test_from_post_zip(self):
124 nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
124 nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
125 nbmodel = requests.get(nbmodel_url).json()
125 nbmodel = requests.get(nbmodel_url).json()
126
126
127 r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel)
127 r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel)
@@ -1,31 +1,17 b''
1 """Tornado handlers for the live notebook view.
1 """Tornado handlers for the live notebook view."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 import os
6 import os
20 from tornado import web
7 from tornado import web
21 HTTPError = web.HTTPError
8 HTTPError = web.HTTPError
22
9
23 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
10 from ..base.handlers import (
24 from ..utils import url_path_join, url_escape
11 IPythonHandler, FilesRedirectHandler,
25
12 notebook_path_regex, path_regex,
26 #-----------------------------------------------------------------------------
13 )
27 # Handlers
14 from ..utils import url_escape
28 #-----------------------------------------------------------------------------
29
15
30
16
31 class NotebookHandler(IPythonHandler):
17 class NotebookHandler(IPythonHandler):
@@ -35,17 +21,16 b' class NotebookHandler(IPythonHandler):'
35 """get renders the notebook template if a name is given, or
21 """get renders the notebook template if a name is given, or
36 redirects to the '/files/' handler if the name is not given."""
22 redirects to the '/files/' handler if the name is not given."""
37 path = path.strip('/')
23 path = path.strip('/')
38 nbm = self.notebook_manager
24 cm = self.contents_manager
39 if name is None:
25 if name is None:
40 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
26 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
41
27
42 # a .ipynb filename was given
28 # a .ipynb filename was given
43 if not nbm.notebook_exists(name, path):
29 if not cm.file_exists(name, path):
44 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
30 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
45 name = url_escape(name)
31 name = url_escape(name)
46 path = url_escape(path)
32 path = url_escape(path)
47 self.write(self.render_template('notebook.html',
33 self.write(self.render_template('notebook.html',
48 project=self.project_dir,
49 notebook_path=path,
34 notebook_path=path,
50 notebook_name=name,
35 notebook_name=name,
51 kill_kernel=False,
36 kill_kernel=False,
@@ -53,30 +38,6 b' class NotebookHandler(IPythonHandler):'
53 )
38 )
54 )
39 )
55
40
56 class NotebookRedirectHandler(IPythonHandler):
57 def get(self, path=''):
58 nbm = self.notebook_manager
59 if nbm.path_exists(path):
60 # it's a *directory*, redirect to /tree
61 url = url_path_join(self.base_url, 'tree', path)
62 else:
63 # otherwise, redirect to /files
64 if '/files/' in path:
65 # redirect without files/ iff it would 404
66 # this preserves pre-2.0-style 'files/' links
67 # FIXME: this is hardcoded based on notebook_path,
68 # but so is the files handler itself,
69 # so it should work until both are cleaned up.
70 parts = path.split('/')
71 files_path = os.path.join(nbm.notebook_dir, *parts)
72 if not os.path.exists(files_path):
73 self.log.warn("Deprecated files/ URL: %s", path)
74 path = path.replace('/files/', '/', 1)
75
76 url = url_path_join(self.base_url, 'files', path)
77 url = url_escape(url)
78 self.log.debug("Redirecting %s to %s", self.request.path, url)
79 self.redirect(url)
80
41
81 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
82 # URL to handler mappings
43 # URL to handler mappings
@@ -85,6 +46,6 b' class NotebookRedirectHandler(IPythonHandler):'
85
46
86 default_handlers = [
47 default_handlers = [
87 (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
48 (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
88 (r"/notebooks%s" % path_regex, NotebookRedirectHandler),
49 (r"/notebooks%s" % path_regex, FilesRedirectHandler),
89 ]
50 ]
90
51
@@ -6,12 +6,14 b''
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import errno
10 import errno
10 import io
11 import io
11 import json
12 import json
12 import logging
13 import logging
13 import os
14 import os
14 import random
15 import random
16 import re
15 import select
17 import select
16 import signal
18 import signal
17 import socket
19 import socket
@@ -53,8 +55,8 b' from IPython.html import DEFAULT_STATIC_FILES_PATH'
53 from .base.handlers import Template404
55 from .base.handlers import Template404
54 from .log import log_request
56 from .log import log_request
55 from .services.kernels.kernelmanager import MappingKernelManager
57 from .services.kernels.kernelmanager import MappingKernelManager
56 from .services.notebooks.nbmanager import NotebookManager
58 from .services.contents.manager import ContentsManager
57 from .services.notebooks.filenbmanager import FileNotebookManager
59 from .services.contents.filemanager import FileContentsManager
58 from .services.clusters.clustermanager import ClusterManager
60 from .services.clusters.clustermanager import ClusterManager
59 from .services.sessions.sessionmanager import SessionManager
61 from .services.sessions.sessionmanager import SessionManager
60
62
@@ -72,6 +74,7 b' from IPython.kernel.zmq.session import default_secure, Session'
72 from IPython.nbformat.sign import NotebookNotary
74 from IPython.nbformat.sign import NotebookNotary
73 from IPython.utils.importstring import import_item
75 from IPython.utils.importstring import import_item
74 from IPython.utils import submodule
76 from IPython.utils import submodule
77 from IPython.utils.process import check_pid
75 from IPython.utils.traitlets import (
78 from IPython.utils.traitlets import (
76 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
79 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
77 DottedObjectName, TraitError,
80 DottedObjectName, TraitError,
@@ -118,19 +121,19 b' def load_handlers(name):'
118
121
119 class NotebookWebApplication(web.Application):
122 class NotebookWebApplication(web.Application):
120
123
121 def __init__(self, ipython_app, kernel_manager, notebook_manager,
124 def __init__(self, ipython_app, kernel_manager, contents_manager,
122 cluster_manager, session_manager, kernel_spec_manager, log,
125 cluster_manager, session_manager, kernel_spec_manager, log,
123 base_url, settings_overrides, jinja_env_options):
126 base_url, settings_overrides, jinja_env_options):
124
127
125 settings = self.init_settings(
128 settings = self.init_settings(
126 ipython_app, kernel_manager, notebook_manager, cluster_manager,
129 ipython_app, kernel_manager, contents_manager, cluster_manager,
127 session_manager, kernel_spec_manager, log, base_url,
130 session_manager, kernel_spec_manager, log, base_url,
128 settings_overrides, jinja_env_options)
131 settings_overrides, jinja_env_options)
129 handlers = self.init_handlers(settings)
132 handlers = self.init_handlers(settings)
130
133
131 super(NotebookWebApplication, self).__init__(handlers, **settings)
134 super(NotebookWebApplication, self).__init__(handlers, **settings)
132
135
133 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
136 def init_settings(self, ipython_app, kernel_manager, contents_manager,
134 cluster_manager, session_manager, kernel_spec_manager,
137 cluster_manager, session_manager, kernel_spec_manager,
135 log, base_url, settings_overrides,
138 log, base_url, settings_overrides,
136 jinja_env_options=None):
139 jinja_env_options=None):
@@ -162,13 +165,14 b' class NotebookWebApplication(web.Application):'
162
165
163 # managers
166 # managers
164 kernel_manager=kernel_manager,
167 kernel_manager=kernel_manager,
165 notebook_manager=notebook_manager,
168 contents_manager=contents_manager,
166 cluster_manager=cluster_manager,
169 cluster_manager=cluster_manager,
167 session_manager=session_manager,
170 session_manager=session_manager,
168 kernel_spec_manager=kernel_spec_manager,
171 kernel_spec_manager=kernel_spec_manager,
169
172
170 # IPython stuff
173 # IPython stuff
171 nbextensions_path = ipython_app.nbextensions_path,
174 nbextensions_path = ipython_app.nbextensions_path,
175 websocket_url=ipython_app.websocket_url,
172 mathjax_url=ipython_app.mathjax_url,
176 mathjax_url=ipython_app.mathjax_url,
173 config=ipython_app.config,
177 config=ipython_app.config,
174 jinja2_env=env,
178 jinja2_env=env,
@@ -189,18 +193,20 b' class NotebookWebApplication(web.Application):'
189 handlers.extend(load_handlers('nbconvert.handlers'))
193 handlers.extend(load_handlers('nbconvert.handlers'))
190 handlers.extend(load_handlers('kernelspecs.handlers'))
194 handlers.extend(load_handlers('kernelspecs.handlers'))
191 handlers.extend(load_handlers('services.kernels.handlers'))
195 handlers.extend(load_handlers('services.kernels.handlers'))
192 handlers.extend(load_handlers('services.notebooks.handlers'))
196 handlers.extend(load_handlers('services.contents.handlers'))
193 handlers.extend(load_handlers('services.clusters.handlers'))
197 handlers.extend(load_handlers('services.clusters.handlers'))
194 handlers.extend(load_handlers('services.sessions.handlers'))
198 handlers.extend(load_handlers('services.sessions.handlers'))
195 handlers.extend(load_handlers('services.nbconvert.handlers'))
199 handlers.extend(load_handlers('services.nbconvert.handlers'))
196 handlers.extend(load_handlers('services.kernelspecs.handlers'))
200 handlers.extend(load_handlers('services.kernelspecs.handlers'))
197 # FIXME: /files/ should be handled by the Contents service when it exists
201 # FIXME: /files/ should be handled by the Contents service when it exists
198 nbm = settings['notebook_manager']
202 cm = settings['contents_manager']
199 if hasattr(nbm, 'notebook_dir'):
203 if hasattr(cm, 'root_dir'):
200 handlers.extend([
204 handlers.append(
201 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
205 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : cm.root_dir}),
206 )
207 handlers.append(
202 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
208 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
203 ])
209 )
204 # prepend base_url onto the patterns that we match
210 # prepend base_url onto the patterns that we match
205 new_handlers = []
211 new_handlers = []
206 for handler in handlers:
212 for handler in handlers:
@@ -260,9 +266,9 b" flags['no-mathjax']=("
260 )
266 )
261
267
262 # Add notebook manager flags
268 # Add notebook manager flags
263 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
269 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
264 'Auto-save a .py script everytime the .ipynb notebook is saved',
270 'DEPRECATED, IGNORED',
265 'Do not auto-save .py scripts for every notebook'))
271 'DEPRECATED, IGNORED'))
266
272
267 aliases = dict(base_aliases)
273 aliases = dict(base_aliases)
268
274
@@ -298,7 +304,7 b' class NotebookApp(BaseIPythonApplication):'
298
304
299 classes = [
305 classes = [
300 KernelManager, ProfileDir, Session, MappingKernelManager,
306 KernelManager, ProfileDir, Session, MappingKernelManager,
301 NotebookManager, FileNotebookManager, NotebookNotary,
307 ContentsManager, FileContentsManager, NotebookNotary,
302 ]
308 ]
303 flags = Dict(flags)
309 flags = Dict(flags)
304 aliases = Dict(aliases)
310 aliases = Dict(aliases)
@@ -333,8 +339,34 b' class NotebookApp(BaseIPythonApplication):'
333 self.file_to_run = base
339 self.file_to_run = base
334 self.notebook_dir = path
340 self.notebook_dir = path
335
341
336 # Network related information.
342 # Network related information
337
343
344 allow_origin = Unicode('', config=True,
345 help="""Set the Access-Control-Allow-Origin header
346
347 Use '*' to allow any origin to access your server.
348
349 Takes precedence over allow_origin_pat.
350 """
351 )
352
353 allow_origin_pat = Unicode('', config=True,
354 help="""Use a regular expression for the Access-Control-Allow-Origin header
355
356 Requests from an origin matching the expression will get replies with:
357
358 Access-Control-Allow-Origin: origin
359
360 where `origin` is the origin of the request.
361
362 Ignored if allow_origin is set.
363 """
364 )
365
366 allow_credentials = Bool(False, config=True,
367 help="Set the Access-Control-Allow-Credentials: true header"
368 )
369
338 ip = Unicode('localhost', config=True,
370 ip = Unicode('localhost', config=True,
339 help="The IP address the notebook server will listen on."
371 help="The IP address the notebook server will listen on."
340 )
372 )
@@ -357,6 +389,14 b' class NotebookApp(BaseIPythonApplication):'
357 help="""The full path to a private key file for usage with SSL/TLS."""
389 help="""The full path to a private key file for usage with SSL/TLS."""
358 )
390 )
359
391
392 cookie_secret_file = Unicode(config=True,
393 help="""The file where the cookie secret is stored."""
394 )
395 def _cookie_secret_file_default(self):
396 if self.profile_dir is None:
397 return ''
398 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
399
360 cookie_secret = Bytes(b'', config=True,
400 cookie_secret = Bytes(b'', config=True,
361 help="""The random bytes used to secure cookies.
401 help="""The random bytes used to secure cookies.
362 By default this is a new random number every time you start the Notebook.
402 By default this is a new random number every time you start the Notebook.
@@ -367,7 +407,26 b' class NotebookApp(BaseIPythonApplication):'
367 """
407 """
368 )
408 )
369 def _cookie_secret_default(self):
409 def _cookie_secret_default(self):
370 return os.urandom(1024)
410 if os.path.exists(self.cookie_secret_file):
411 with io.open(self.cookie_secret_file, 'rb') as f:
412 return f.read()
413 else:
414 secret = base64.encodestring(os.urandom(1024))
415 self._write_cookie_secret_file(secret)
416 return secret
417
418 def _write_cookie_secret_file(self, secret):
419 """write my secret to my secret_file"""
420 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
421 with io.open(self.cookie_secret_file, 'wb') as f:
422 f.write(secret)
423 try:
424 os.chmod(self.cookie_secret_file, 0o600)
425 except OSError:
426 self.log.warn(
427 "Could not set permissions on %s",
428 self.cookie_secret_file
429 )
371
430
372 password = Unicode(u'', config=True,
431 password = Unicode(u'', config=True,
373 help="""Hashed password to use for web authentication.
432 help="""Hashed password to use for web authentication.
@@ -456,6 +515,13 b' class NotebookApp(BaseIPythonApplication):'
456 def _nbextensions_path_default(self):
515 def _nbextensions_path_default(self):
457 return [os.path.join(get_ipython_dir(), 'nbextensions')]
516 return [os.path.join(get_ipython_dir(), 'nbextensions')]
458
517
518 websocket_url = Unicode("", config=True,
519 help="""The base URL for websockets,
520 if it differs from the HTTP server (hint: it almost certainly doesn't).
521
522 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
523 """
524 )
459 mathjax_url = Unicode("", config=True,
525 mathjax_url = Unicode("", config=True,
460 help="""The url for MathJax.js."""
526 help="""The url for MathJax.js."""
461 )
527 )
@@ -482,13 +548,7 b' class NotebookApp(BaseIPythonApplication):'
482 return url
548 return url
483
549
484 # no local mathjax, serve from CDN
550 # no local mathjax, serve from CDN
485 if self.certfile:
551 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
486 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
487 host = u"https://c328740.ssl.cf1.rackcdn.com"
488 else:
489 host = u"http://cdn.mathjax.org"
490
491 url = host + u"/mathjax/latest/MathJax.js"
492 self.log.info("Using MathJax from CDN: %s", url)
552 self.log.info("Using MathJax from CDN: %s", url)
493 return url
553 return url
494
554
@@ -499,7 +559,7 b' class NotebookApp(BaseIPythonApplication):'
499 else:
559 else:
500 self.log.info("Using MathJax: %s", new)
560 self.log.info("Using MathJax: %s", new)
501
561
502 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
562 contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
503 config=True,
563 config=True,
504 help='The notebook manager class to use.'
564 help='The notebook manager class to use.'
505 )
565 )
@@ -563,7 +623,7 b' class NotebookApp(BaseIPythonApplication):'
563 raise TraitError("No such notebook dir: %r" % new)
623 raise TraitError("No such notebook dir: %r" % new)
564
624
565 # setting App.notebook_dir implies setting notebook and kernel dirs as well
625 # setting App.notebook_dir implies setting notebook and kernel dirs as well
566 self.config.FileNotebookManager.notebook_dir = new
626 self.config.FileContentsManager.root_dir = new
567 self.config.MappingKernelManager.root_dir = new
627 self.config.MappingKernelManager.root_dir = new
568
628
569
629
@@ -589,11 +649,8 b' class NotebookApp(BaseIPythonApplication):'
589
649
590 def init_kernel_argv(self):
650 def init_kernel_argv(self):
591 """construct the kernel arguments"""
651 """construct the kernel arguments"""
592 self.kernel_argv = []
593 # Kernel should inherit default config file from frontend
594 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
595 # Kernel should get *absolute* path to profile directory
652 # Kernel should get *absolute* path to profile directory
596 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
653 self.kernel_argv = ["--profile-dir", self.profile_dir.location]
597
654
598 def init_configurables(self):
655 def init_configurables(self):
599 # force Session default to be secure
656 # force Session default to be secure
@@ -603,10 +660,12 b' class NotebookApp(BaseIPythonApplication):'
603 parent=self, log=self.log, kernel_argv=self.kernel_argv,
660 parent=self, log=self.log, kernel_argv=self.kernel_argv,
604 connection_dir = self.profile_dir.security_dir,
661 connection_dir = self.profile_dir.security_dir,
605 )
662 )
606 kls = import_item(self.notebook_manager_class)
663 kls = import_item(self.contents_manager_class)
607 self.notebook_manager = kls(parent=self, log=self.log)
664 self.contents_manager = kls(parent=self, log=self.log)
608 kls = import_item(self.session_manager_class)
665 kls = import_item(self.session_manager_class)
609 self.session_manager = kls(parent=self, log=self.log)
666 self.session_manager = kls(parent=self, log=self.log,
667 kernel_manager=self.kernel_manager,
668 contents_manager=self.contents_manager)
610 kls = import_item(self.cluster_manager_class)
669 kls = import_item(self.cluster_manager_class)
611 self.cluster_manager = kls(parent=self, log=self.log)
670 self.cluster_manager = kls(parent=self, log=self.log)
612 self.cluster_manager.update_profiles()
671 self.cluster_manager.update_profiles()
@@ -625,8 +684,13 b' class NotebookApp(BaseIPythonApplication):'
625
684
626 def init_webapp(self):
685 def init_webapp(self):
627 """initialize tornado webapp and httpserver"""
686 """initialize tornado webapp and httpserver"""
687 self.webapp_settings['allow_origin'] = self.allow_origin
688 if self.allow_origin_pat:
689 self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
690 self.webapp_settings['allow_credentials'] = self.allow_credentials
691
628 self.web_app = NotebookWebApplication(
692 self.web_app = NotebookWebApplication(
629 self, self.kernel_manager, self.notebook_manager,
693 self, self.kernel_manager, self.contents_manager,
630 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
694 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
631 self.log, self.base_url, self.webapp_settings,
695 self.log, self.base_url, self.webapp_settings,
632 self.jinja_environment_options
696 self.jinja_environment_options
@@ -717,8 +781,6 b' class NotebookApp(BaseIPythonApplication):'
717
781
718 This doesn't work on Windows.
782 This doesn't work on Windows.
719 """
783 """
720 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
721 time.sleep(0.1)
722 info = self.log.info
784 info = self.log.info
723 info('interrupted')
785 info('interrupted')
724 print(self.notebook_info())
786 print(self.notebook_info())
@@ -778,7 +840,7 b' class NotebookApp(BaseIPythonApplication):'
778
840
779 def notebook_info(self):
841 def notebook_info(self):
780 "Return the current working directory and the server url information"
842 "Return the current working directory and the server url information"
781 info = self.notebook_manager.info_string() + "\n"
843 info = self.contents_manager.info_string() + "\n"
782 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
844 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
783 return info + "The IPython Notebook is running at: %s" % self.display_url
845 return info + "The IPython Notebook is running at: %s" % self.display_url
784
846
@@ -790,6 +852,7 b' class NotebookApp(BaseIPythonApplication):'
790 'secure': bool(self.certfile),
852 'secure': bool(self.certfile),
791 'base_url': self.base_url,
853 'base_url': self.base_url,
792 'notebook_dir': os.path.abspath(self.notebook_dir),
854 'notebook_dir': os.path.abspath(self.notebook_dir),
855 'pid': os.getpid()
793 }
856 }
794
857
795 def write_server_info_file(self):
858 def write_server_info_file(self):
@@ -863,8 +926,17 b" def list_running_servers(profile='default'):"
863 for file in os.listdir(pd.security_dir):
926 for file in os.listdir(pd.security_dir):
864 if file.startswith('nbserver-'):
927 if file.startswith('nbserver-'):
865 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
928 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
866 yield json.load(f)
929 info = json.load(f)
867
930
931 # Simple check whether that process is really still running
932 if check_pid(info['pid']):
933 yield info
934 else:
935 # If the process has died, try to delete its info file
936 try:
937 os.unlink(file)
938 except OSError:
939 pass # TODO: This should warn or log or something
868 #-----------------------------------------------------------------------------
940 #-----------------------------------------------------------------------------
869 # Main entry point
941 # Main entry point
870 #-----------------------------------------------------------------------------
942 #-----------------------------------------------------------------------------
@@ -21,7 +21,6 b' from zmq.eventloop import ioloop'
21
21
22 from IPython.config.configurable import LoggingConfigurable
22 from IPython.config.configurable import LoggingConfigurable
23 from IPython.utils.traitlets import Dict, Instance, CFloat
23 from IPython.utils.traitlets import Dict, Instance, CFloat
24 from IPython.parallel.apps.ipclusterapp import IPClusterStart
25 from IPython.core.profileapp import list_profiles_in
24 from IPython.core.profileapp import list_profiles_in
26 from IPython.core.profiledir import ProfileDir
25 from IPython.core.profiledir import ProfileDir
27 from IPython.utils import py3compat
26 from IPython.utils import py3compat
@@ -33,17 +32,6 b' from IPython.utils.path import get_ipython_dir'
33 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
34
33
35
34
36 class DummyIPClusterStart(IPClusterStart):
37 """Dummy subclass to skip init steps that conflict with global app.
38
39 Instantiating and initializing this class should result in fully configured
40 launchers, but no other side effects or state.
41 """
42
43 def init_signal(self):
44 pass
45 def reinit_logging(self):
46 pass
47
35
48
36
49 class ClusterManager(LoggingConfigurable):
37 class ClusterManager(LoggingConfigurable):
@@ -59,6 +47,20 b' class ClusterManager(LoggingConfigurable):'
59 return IOLoop.instance()
47 return IOLoop.instance()
60
48
61 def build_launchers(self, profile_dir):
49 def build_launchers(self, profile_dir):
50 from IPython.parallel.apps.ipclusterapp import IPClusterStart
51
52 class DummyIPClusterStart(IPClusterStart):
53 """Dummy subclass to skip init steps that conflict with global app.
54
55 Instantiating and initializing this class should result in fully configured
56 launchers, but no other side effects or state.
57 """
58
59 def init_signal(self):
60 pass
61 def reinit_logging(self):
62 pass
63
62 starter = DummyIPClusterStart(log=self.log)
64 starter = DummyIPClusterStart(log=self.log)
63 starter.initialize(['--profile-dir', profile_dir])
65 starter.initialize(['--profile-dir', profile_dir])
64 cl = starter.controller_launcher
66 cl = starter.controller_launcher
1 NO CONTENT: file renamed from IPython/html/services/notebooks/__init__.py to IPython/html/services/contents/__init__.py
NO CONTENT: file renamed from IPython/html/services/notebooks/__init__.py to IPython/html/services/contents/__init__.py
1 NO CONTENT: file renamed from IPython/html/services/notebooks/tests/__init__.py to IPython/html/services/contents/tests/__init__.py
NO CONTENT: file renamed from IPython/html/services/notebooks/tests/__init__.py to IPython/html/services/contents/tests/__init__.py
@@ -1,6 +1,7 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Test the notebooks webservice API."""
2 """Test the contents webservice API."""
3
3
4 import base64
4 import io
5 import io
5 import json
6 import json
6 import os
7 import os
@@ -21,23 +22,21 b' from IPython.utils import py3compat'
21 from IPython.utils.data import uniq_stable
22 from IPython.utils.data import uniq_stable
22
23
23
24
24 # TODO: Remove this after we create the contents web service and directories are
25 def notebooks_only(dir_model):
25 # no longer listed by the notebook web service.
26 return [nb for nb in dir_model['content'] if nb['type']=='notebook']
26 def notebooks_only(nb_list):
27 return [nb for nb in nb_list if nb['type']=='notebook']
28
27
29 def dirs_only(nb_list):
28 def dirs_only(dir_model):
30 return [x for x in nb_list if x['type']=='directory']
29 return [x for x in dir_model['content'] if x['type']=='directory']
31
30
32
31
33 class NBAPI(object):
32 class API(object):
34 """Wrapper for notebook API calls."""
33 """Wrapper for contents API calls."""
35 def __init__(self, base_url):
34 def __init__(self, base_url):
36 self.base_url = base_url
35 self.base_url = base_url
37
36
38 def _req(self, verb, path, body=None):
37 def _req(self, verb, path, body=None):
39 response = requests.request(verb,
38 response = requests.request(verb,
40 url_path_join(self.base_url, 'api/notebooks', path),
39 url_path_join(self.base_url, 'api/contents', path),
41 data=body,
40 data=body,
42 )
41 )
43 response.raise_for_status()
42 response.raise_for_status()
@@ -49,8 +48,11 b' class NBAPI(object):'
49 def read(self, name, path='/'):
48 def read(self, name, path='/'):
50 return self._req('GET', url_path_join(path, name))
49 return self._req('GET', url_path_join(path, name))
51
50
52 def create_untitled(self, path='/'):
51 def create_untitled(self, path='/', ext=None):
53 return self._req('POST', path)
52 body = None
53 if ext:
54 body = json.dumps({'ext': ext})
55 return self._req('POST', path, body)
54
56
55 def upload_untitled(self, body, path='/'):
57 def upload_untitled(self, body, path='/'):
56 return self._req('POST', path, body)
58 return self._req('POST', path, body)
@@ -65,6 +67,9 b' class NBAPI(object):'
65 def upload(self, name, body, path='/'):
67 def upload(self, name, body, path='/'):
66 return self._req('PUT', url_path_join(path, name), body)
68 return self._req('PUT', url_path_join(path, name), body)
67
69
70 def mkdir(self, name, path='/'):
71 return self._req('PUT', url_path_join(path, name), json.dumps({'type': 'directory'}))
72
68 def copy(self, copy_from, copy_to, path='/'):
73 def copy(self, copy_from, copy_to, path='/'):
69 body = json.dumps({'copy_from':copy_from})
74 body = json.dumps({'copy_from':copy_from})
70 return self._req('PUT', url_path_join(path, copy_to), body)
75 return self._req('PUT', url_path_join(path, copy_to), body)
@@ -112,8 +117,20 b' class APITest(NotebookTestBase):'
112 del dirs[0] # remove ''
117 del dirs[0] # remove ''
113 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
118 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
114
119
120 @staticmethod
121 def _blob_for_name(name):
122 return name.encode('utf-8') + b'\xFF'
123
124 @staticmethod
125 def _txt_for_name(name):
126 return u'%s text file' % name
127
115 def setUp(self):
128 def setUp(self):
116 nbdir = self.notebook_dir.name
129 nbdir = self.notebook_dir.name
130 self.blob = os.urandom(100)
131 self.b64_blob = base64.encodestring(self.blob).decode('ascii')
132
133
117
134
118 for d in (self.dirs + self.hidden_dirs):
135 for d in (self.dirs + self.hidden_dirs):
119 d.replace('/', os.sep)
136 d.replace('/', os.sep)
@@ -122,12 +139,22 b' class APITest(NotebookTestBase):'
122
139
123 for d, name in self.dirs_nbs:
140 for d, name in self.dirs_nbs:
124 d = d.replace('/', os.sep)
141 d = d.replace('/', os.sep)
142 # create a notebook
125 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
143 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
126 encoding='utf-8') as f:
144 encoding='utf-8') as f:
127 nb = new_notebook(name=name)
145 nb = new_notebook(name=name)
128 write(nb, f, format='ipynb')
146 write(nb, f, format='ipynb')
129
147
130 self.nb_api = NBAPI(self.base_url())
148 # create a text file
149 with io.open(pjoin(nbdir, d, '%s.txt' % name), 'w',
150 encoding='utf-8') as f:
151 f.write(self._txt_for_name(name))
152
153 # create a binary file
154 with io.open(pjoin(nbdir, d, '%s.blob' % name), 'wb') as f:
155 f.write(self._blob_for_name(name))
156
157 self.api = API(self.base_url())
131
158
132 def tearDown(self):
159 def tearDown(self):
133 nbdir = self.notebook_dir.name
160 nbdir = self.notebook_dir.name
@@ -139,175 +166,287 b' class APITest(NotebookTestBase):'
139 os.unlink(pjoin(nbdir, 'inroot.ipynb'))
166 os.unlink(pjoin(nbdir, 'inroot.ipynb'))
140
167
141 def test_list_notebooks(self):
168 def test_list_notebooks(self):
142 nbs = notebooks_only(self.nb_api.list().json())
169 nbs = notebooks_only(self.api.list().json())
143 self.assertEqual(len(nbs), 1)
170 self.assertEqual(len(nbs), 1)
144 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
171 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
145
172
146 nbs = notebooks_only(self.nb_api.list('/Directory with spaces in/').json())
173 nbs = notebooks_only(self.api.list('/Directory with spaces in/').json())
147 self.assertEqual(len(nbs), 1)
174 self.assertEqual(len(nbs), 1)
148 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
175 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
149
176
150 nbs = notebooks_only(self.nb_api.list(u'/unicodé/').json())
177 nbs = notebooks_only(self.api.list(u'/unicodé/').json())
151 self.assertEqual(len(nbs), 1)
178 self.assertEqual(len(nbs), 1)
152 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
179 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
153 self.assertEqual(nbs[0]['path'], u'unicodé')
180 self.assertEqual(nbs[0]['path'], u'unicodé')
154
181
155 nbs = notebooks_only(self.nb_api.list('/foo/bar/').json())
182 nbs = notebooks_only(self.api.list('/foo/bar/').json())
156 self.assertEqual(len(nbs), 1)
183 self.assertEqual(len(nbs), 1)
157 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
184 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
158 self.assertEqual(nbs[0]['path'], 'foo/bar')
185 self.assertEqual(nbs[0]['path'], 'foo/bar')
159
186
160 nbs = notebooks_only(self.nb_api.list('foo').json())
187 nbs = notebooks_only(self.api.list('foo').json())
161 self.assertEqual(len(nbs), 4)
188 self.assertEqual(len(nbs), 4)
162 nbnames = { normalize('NFC', n['name']) for n in nbs }
189 nbnames = { normalize('NFC', n['name']) for n in nbs }
163 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb']
190 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb']
164 expected = { normalize('NFC', name) for name in expected }
191 expected = { normalize('NFC', name) for name in expected }
165 self.assertEqual(nbnames, expected)
192 self.assertEqual(nbnames, expected)
166
193
167 nbs = notebooks_only(self.nb_api.list('ordering').json())
194 nbs = notebooks_only(self.api.list('ordering').json())
168 nbnames = [n['name'] for n in nbs]
195 nbnames = [n['name'] for n in nbs]
169 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
196 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
170 self.assertEqual(nbnames, expected)
197 self.assertEqual(nbnames, expected)
171
198
172 def test_list_dirs(self):
199 def test_list_dirs(self):
173 dirs = dirs_only(self.nb_api.list().json())
200 dirs = dirs_only(self.api.list().json())
174 dir_names = {normalize('NFC', d['name']) for d in dirs}
201 dir_names = {normalize('NFC', d['name']) for d in dirs}
175 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
202 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
176
203
177 def test_list_nonexistant_dir(self):
204 def test_list_nonexistant_dir(self):
178 with assert_http_error(404):
205 with assert_http_error(404):
179 self.nb_api.list('nonexistant')
206 self.api.list('nonexistant')
180
207
181 def test_get_contents(self):
208 def test_get_nb_contents(self):
182 for d, name in self.dirs_nbs:
209 for d, name in self.dirs_nbs:
183 nb = self.nb_api.read('%s.ipynb' % name, d+'/').json()
210 nb = self.api.read('%s.ipynb' % name, d+'/').json()
184 self.assertEqual(nb['name'], u'%s.ipynb' % name)
211 self.assertEqual(nb['name'], u'%s.ipynb' % name)
212 self.assertEqual(nb['type'], 'notebook')
213 self.assertIn('content', nb)
214 self.assertEqual(nb['format'], 'json')
185 self.assertIn('content', nb)
215 self.assertIn('content', nb)
186 self.assertIn('metadata', nb['content'])
216 self.assertIn('metadata', nb['content'])
187 self.assertIsInstance(nb['content']['metadata'], dict)
217 self.assertIsInstance(nb['content']['metadata'], dict)
188
218
219 def test_get_contents_no_such_file(self):
220 # Name that doesn't exist - should be a 404
221 with assert_http_error(404):
222 self.api.read('q.ipynb', 'foo')
223
224 def test_get_text_file_contents(self):
225 for d, name in self.dirs_nbs:
226 model = self.api.read(u'%s.txt' % name, d+'/').json()
227 self.assertEqual(model['name'], u'%s.txt' % name)
228 self.assertIn('content', model)
229 self.assertEqual(model['format'], 'text')
230 self.assertEqual(model['type'], 'file')
231 self.assertEqual(model['content'], self._txt_for_name(name))
232
233 # Name that doesn't exist - should be a 404
234 with assert_http_error(404):
235 self.api.read('q.txt', 'foo')
236
237 def test_get_binary_file_contents(self):
238 for d, name in self.dirs_nbs:
239 model = self.api.read(u'%s.blob' % name, d+'/').json()
240 self.assertEqual(model['name'], u'%s.blob' % name)
241 self.assertIn('content', model)
242 self.assertEqual(model['format'], 'base64')
243 self.assertEqual(model['type'], 'file')
244 b64_data = base64.encodestring(self._blob_for_name(name)).decode('ascii')
245 self.assertEqual(model['content'], b64_data)
246
189 # Name that doesn't exist - should be a 404
247 # Name that doesn't exist - should be a 404
190 with assert_http_error(404):
248 with assert_http_error(404):
191 self.nb_api.read('q.ipynb', 'foo')
249 self.api.read('q.txt', 'foo')
192
250
193 def _check_nb_created(self, resp, name, path):
251 def _check_created(self, resp, name, path, type='notebook'):
194 self.assertEqual(resp.status_code, 201)
252 self.assertEqual(resp.status_code, 201)
195 location_header = py3compat.str_to_unicode(resp.headers['Location'])
253 location_header = py3compat.str_to_unicode(resp.headers['Location'])
196 self.assertEqual(location_header, url_escape(url_path_join(u'/api/notebooks', path, name)))
254 self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path, name)))
197 self.assertEqual(resp.json()['name'], name)
255 rjson = resp.json()
198 assert os.path.isfile(pjoin(
256 self.assertEqual(rjson['name'], name)
257 self.assertEqual(rjson['path'], path)
258 self.assertEqual(rjson['type'], type)
259 isright = os.path.isdir if type == 'directory' else os.path.isfile
260 assert isright(pjoin(
199 self.notebook_dir.name,
261 self.notebook_dir.name,
200 path.replace('/', os.sep),
262 path.replace('/', os.sep),
201 name,
263 name,
202 ))
264 ))
203
265
204 def test_create_untitled(self):
266 def test_create_untitled(self):
205 resp = self.nb_api.create_untitled(path=u'å b')
267 resp = self.api.create_untitled(path=u'å b')
206 self._check_nb_created(resp, 'Untitled0.ipynb', u'å b')
268 self._check_created(resp, 'Untitled0.ipynb', u'å b')
207
269
208 # Second time
270 # Second time
209 resp = self.nb_api.create_untitled(path=u'å b')
271 resp = self.api.create_untitled(path=u'å b')
210 self._check_nb_created(resp, 'Untitled1.ipynb', u'å b')
272 self._check_created(resp, 'Untitled1.ipynb', u'å b')
211
273
212 # And two directories down
274 # And two directories down
213 resp = self.nb_api.create_untitled(path='foo/bar')
275 resp = self.api.create_untitled(path='foo/bar')
214 self._check_nb_created(resp, 'Untitled0.ipynb', 'foo/bar')
276 self._check_created(resp, 'Untitled0.ipynb', 'foo/bar')
277
278 def test_create_untitled_txt(self):
279 resp = self.api.create_untitled(path='foo/bar', ext='.txt')
280 self._check_created(resp, 'untitled0.txt', 'foo/bar', type='file')
281
282 resp = self.api.read(path='foo/bar', name='untitled0.txt')
283 model = resp.json()
284 self.assertEqual(model['type'], 'file')
285 self.assertEqual(model['format'], 'text')
286 self.assertEqual(model['content'], '')
215
287
216 def test_upload_untitled(self):
288 def test_upload_untitled(self):
217 nb = new_notebook(name='Upload test')
289 nb = new_notebook(name='Upload test')
218 nbmodel = {'content': nb}
290 nbmodel = {'content': nb, 'type': 'notebook'}
219 resp = self.nb_api.upload_untitled(path=u'å b',
291 resp = self.api.upload_untitled(path=u'å b',
220 body=json.dumps(nbmodel))
292 body=json.dumps(nbmodel))
221 self._check_nb_created(resp, 'Untitled0.ipynb', u'å b')
293 self._check_created(resp, 'Untitled0.ipynb', u'å b')
222
294
223 def test_upload(self):
295 def test_upload(self):
224 nb = new_notebook(name=u'ignored')
296 nb = new_notebook(name=u'ignored')
225 nbmodel = {'content': nb}
297 nbmodel = {'content': nb, 'type': 'notebook'}
226 resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
298 resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
227 body=json.dumps(nbmodel))
299 body=json.dumps(nbmodel))
228 self._check_nb_created(resp, u'Upload tést.ipynb', u'å b')
300 self._check_created(resp, u'Upload tést.ipynb', u'å b')
301
302 def test_mkdir(self):
303 resp = self.api.mkdir(u'New ∂ir', path=u'å b')
304 self._check_created(resp, u'New ∂ir', u'å b', type='directory')
305
306 def test_mkdir_hidden_400(self):
307 with assert_http_error(400):
308 resp = self.api.mkdir(u'.hidden', path=u'å b')
309
310 def test_upload_txt(self):
311 body = u'ünicode téxt'
312 model = {
313 'content' : body,
314 'format' : 'text',
315 'type' : 'file',
316 }
317 resp = self.api.upload(u'Upload tést.txt', path=u'å b',
318 body=json.dumps(model))
319
320 # check roundtrip
321 resp = self.api.read(path=u'å b', name=u'Upload tést.txt')
322 model = resp.json()
323 self.assertEqual(model['type'], 'file')
324 self.assertEqual(model['format'], 'text')
325 self.assertEqual(model['content'], body)
326
327 def test_upload_b64(self):
328 body = b'\xFFblob'
329 b64body = base64.encodestring(body).decode('ascii')
330 model = {
331 'content' : b64body,
332 'format' : 'base64',
333 'type' : 'file',
334 }
335 resp = self.api.upload(u'Upload tést.blob', path=u'å b',
336 body=json.dumps(model))
337
338 # check roundtrip
339 resp = self.api.read(path=u'å b', name=u'Upload tést.blob')
340 model = resp.json()
341 self.assertEqual(model['type'], 'file')
342 self.assertEqual(model['format'], 'base64')
343 decoded = base64.decodestring(model['content'].encode('ascii'))
344 self.assertEqual(decoded, body)
229
345
230 def test_upload_v2(self):
346 def test_upload_v2(self):
231 nb = v2.new_notebook()
347 nb = v2.new_notebook()
232 ws = v2.new_worksheet()
348 ws = v2.new_worksheet()
233 nb.worksheets.append(ws)
349 nb.worksheets.append(ws)
234 ws.cells.append(v2.new_code_cell(input='print("hi")'))
350 ws.cells.append(v2.new_code_cell(input='print("hi")'))
235 nbmodel = {'content': nb}
351 nbmodel = {'content': nb, 'type': 'notebook'}
236 resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
352 resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
237 body=json.dumps(nbmodel))
353 body=json.dumps(nbmodel))
238 self._check_nb_created(resp, u'Upload tést.ipynb', u'å b')
354 self._check_created(resp, u'Upload tést.ipynb', u'å b')
239 resp = self.nb_api.read(u'Upload tést.ipynb', u'å b')
355 resp = self.api.read(u'Upload tést.ipynb', u'å b')
240 data = resp.json()
356 data = resp.json()
241 self.assertEqual(data['content']['nbformat'], current.nbformat)
357 self.assertEqual(data['content']['nbformat'], current.nbformat)
242 self.assertEqual(data['content']['orig_nbformat'], 2)
358 self.assertEqual(data['content']['orig_nbformat'], 2)
243
359
244 def test_copy_untitled(self):
360 def test_copy_untitled(self):
245 resp = self.nb_api.copy_untitled(u'ç d.ipynb', path=u'å b')
361 resp = self.api.copy_untitled(u'ç d.ipynb', path=u'å b')
246 self._check_nb_created(resp, u'ç d-Copy0.ipynb', u'å b')
362 self._check_created(resp, u'ç d-Copy0.ipynb', u'å b')
247
363
248 def test_copy(self):
364 def test_copy(self):
249 resp = self.nb_api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
365 resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
250 self._check_nb_created(resp, u'cøpy.ipynb', u'å b')
366 self._check_created(resp, u'cøpy.ipynb', u'å b')
367
368 def test_copy_path(self):
369 resp = self.api.copy(u'foo/a.ipynb', u'cøpyfoo.ipynb', path=u'å b')
370 self._check_created(resp, u'cøpyfoo.ipynb', u'å b')
371
372 def test_copy_dir_400(self):
373 # can't copy directories
374 with assert_http_error(400):
375 resp = self.api.copy(u'å b', u'å c')
251
376
252 def test_delete(self):
377 def test_delete(self):
253 for d, name in self.dirs_nbs:
378 for d, name in self.dirs_nbs:
254 resp = self.nb_api.delete('%s.ipynb' % name, d)
379 resp = self.api.delete('%s.ipynb' % name, d)
255 self.assertEqual(resp.status_code, 204)
380 self.assertEqual(resp.status_code, 204)
256
381
257 for d in self.dirs + ['/']:
382 for d in self.dirs + ['/']:
258 nbs = notebooks_only(self.nb_api.list(d).json())
383 nbs = notebooks_only(self.api.list(d).json())
259 self.assertEqual(len(nbs), 0)
384 self.assertEqual(len(nbs), 0)
260
385
386 def test_delete_dirs(self):
387 # depth-first delete everything, so we don't try to delete empty directories
388 for name in sorted(self.dirs + ['/'], key=len, reverse=True):
389 listing = self.api.list(name).json()['content']
390 for model in listing:
391 self.api.delete(model['name'], model['path'])
392 listing = self.api.list('/').json()['content']
393 self.assertEqual(listing, [])
394
395 def test_delete_non_empty_dir(self):
396 """delete non-empty dir raises 400"""
397 with assert_http_error(400):
398 self.api.delete(u'å b')
399
261 def test_rename(self):
400 def test_rename(self):
262 resp = self.nb_api.rename('a.ipynb', 'foo', 'z.ipynb')
401 resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb')
263 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
402 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
264 self.assertEqual(resp.json()['name'], 'z.ipynb')
403 self.assertEqual(resp.json()['name'], 'z.ipynb')
265 assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
404 assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
266
405
267 nbs = notebooks_only(self.nb_api.list('foo').json())
406 nbs = notebooks_only(self.api.list('foo').json())
268 nbnames = set(n['name'] for n in nbs)
407 nbnames = set(n['name'] for n in nbs)
269 self.assertIn('z.ipynb', nbnames)
408 self.assertIn('z.ipynb', nbnames)
270 self.assertNotIn('a.ipynb', nbnames)
409 self.assertNotIn('a.ipynb', nbnames)
271
410
272 def test_rename_existing(self):
411 def test_rename_existing(self):
273 with assert_http_error(409):
412 with assert_http_error(409):
274 self.nb_api.rename('a.ipynb', 'foo', 'b.ipynb')
413 self.api.rename('a.ipynb', 'foo', 'b.ipynb')
275
414
276 def test_save(self):
415 def test_save(self):
277 resp = self.nb_api.read('a.ipynb', 'foo')
416 resp = self.api.read('a.ipynb', 'foo')
278 nbcontent = json.loads(resp.text)['content']
417 nbcontent = json.loads(resp.text)['content']
279 nb = to_notebook_json(nbcontent)
418 nb = to_notebook_json(nbcontent)
280 ws = new_worksheet()
419 ws = new_worksheet()
281 nb.worksheets = [ws]
420 nb.worksheets = [ws]
282 ws.cells.append(new_heading_cell(u'Created by test ³'))
421 ws.cells.append(new_heading_cell(u'Created by test ³'))
283
422
284 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
423 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
285 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
424 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
286
425
287 nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
426 nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
288 with io.open(nbfile, 'r', encoding='utf-8') as f:
427 with io.open(nbfile, 'r', encoding='utf-8') as f:
289 newnb = read(f, format='ipynb')
428 newnb = read(f, format='ipynb')
290 self.assertEqual(newnb.worksheets[0].cells[0].source,
429 self.assertEqual(newnb.worksheets[0].cells[0].source,
291 u'Created by test ³')
430 u'Created by test ³')
292 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
431 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
293 newnb = to_notebook_json(nbcontent)
432 newnb = to_notebook_json(nbcontent)
294 self.assertEqual(newnb.worksheets[0].cells[0].source,
433 self.assertEqual(newnb.worksheets[0].cells[0].source,
295 u'Created by test ³')
434 u'Created by test ³')
296
435
297 # Save and rename
436 # Save and rename
298 nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb}
437 nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb, 'type': 'notebook'}
299 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
438 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
300 saved = resp.json()
439 saved = resp.json()
301 self.assertEqual(saved['name'], 'a2.ipynb')
440 self.assertEqual(saved['name'], 'a2.ipynb')
302 self.assertEqual(saved['path'], 'foo/bar')
441 self.assertEqual(saved['path'], 'foo/bar')
303 assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
442 assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
304 assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
443 assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
305 with assert_http_error(404):
444 with assert_http_error(404):
306 self.nb_api.read('a.ipynb', 'foo')
445 self.api.read('a.ipynb', 'foo')
307
446
308 def test_checkpoints(self):
447 def test_checkpoints(self):
309 resp = self.nb_api.read('a.ipynb', 'foo')
448 resp = self.api.read('a.ipynb', 'foo')
310 r = self.nb_api.new_checkpoint('a.ipynb', 'foo')
449 r = self.api.new_checkpoint('a.ipynb', 'foo')
311 self.assertEqual(r.status_code, 201)
450 self.assertEqual(r.status_code, 201)
312 cp1 = r.json()
451 cp1 = r.json()
313 self.assertEqual(set(cp1), {'id', 'last_modified'})
452 self.assertEqual(set(cp1), {'id', 'last_modified'})
@@ -321,27 +460,26 b' class APITest(NotebookTestBase):'
321 hcell = new_heading_cell('Created by test')
460 hcell = new_heading_cell('Created by test')
322 ws.cells.append(hcell)
461 ws.cells.append(hcell)
323 # Save
462 # Save
324 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
463 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
325 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
464 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
326
465
327 # List checkpoints
466 # List checkpoints
328 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
467 cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
329 self.assertEqual(cps, [cp1])
468 self.assertEqual(cps, [cp1])
330
469
331 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
470 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
332 nb = to_notebook_json(nbcontent)
471 nb = to_notebook_json(nbcontent)
333 self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
472 self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
334
473
335 # Restore cp1
474 # Restore cp1
336 r = self.nb_api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
475 r = self.api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
337 self.assertEqual(r.status_code, 204)
476 self.assertEqual(r.status_code, 204)
338 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
477 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
339 nb = to_notebook_json(nbcontent)
478 nb = to_notebook_json(nbcontent)
340 self.assertEqual(nb.worksheets, [])
479 self.assertEqual(nb.worksheets, [])
341
480
342 # Delete cp1
481 # Delete cp1
343 r = self.nb_api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
482 r = self.api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
344 self.assertEqual(r.status_code, 204)
483 self.assertEqual(r.status_code, 204)
345 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
484 cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
346 self.assertEqual(cps, [])
485 self.assertEqual(cps, [])
347
@@ -15,74 +15,74 b' from IPython.utils.tempdir import TemporaryDirectory'
15 from IPython.utils.traitlets import TraitError
15 from IPython.utils.traitlets import TraitError
16 from IPython.html.utils import url_path_join
16 from IPython.html.utils import url_path_join
17
17
18 from ..filenbmanager import FileNotebookManager
18 from ..filemanager import FileContentsManager
19 from ..nbmanager import NotebookManager
19 from ..manager import ContentsManager
20
20
21
21
22 class TestFileNotebookManager(TestCase):
22 class TestFileContentsManager(TestCase):
23
23
24 def test_nb_dir(self):
24 def test_root_dir(self):
25 with TemporaryDirectory() as td:
25 with TemporaryDirectory() as td:
26 fm = FileNotebookManager(notebook_dir=td)
26 fm = FileContentsManager(root_dir=td)
27 self.assertEqual(fm.notebook_dir, td)
27 self.assertEqual(fm.root_dir, td)
28
28
29 def test_missing_nb_dir(self):
29 def test_missing_root_dir(self):
30 with TemporaryDirectory() as td:
30 with TemporaryDirectory() as td:
31 nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
31 root = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
32 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir)
32 self.assertRaises(TraitError, FileContentsManager, root_dir=root)
33
33
34 def test_invalid_nb_dir(self):
34 def test_invalid_root_dir(self):
35 with NamedTemporaryFile() as tf:
35 with NamedTemporaryFile() as tf:
36 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=tf.name)
36 self.assertRaises(TraitError, FileContentsManager, root_dir=tf.name)
37
37
38 def test_get_os_path(self):
38 def test_get_os_path(self):
39 # full filesystem path should be returned with correct operating system
39 # full filesystem path should be returned with correct operating system
40 # separators.
40 # separators.
41 with TemporaryDirectory() as td:
41 with TemporaryDirectory() as td:
42 nbdir = td
42 root = td
43 fm = FileNotebookManager(notebook_dir=nbdir)
43 fm = FileContentsManager(root_dir=root)
44 path = fm._get_os_path('test.ipynb', '/path/to/notebook/')
44 path = fm._get_os_path('test.ipynb', '/path/to/notebook/')
45 rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
45 rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
46 fs_path = os.path.join(fm.notebook_dir, *rel_path_list)
46 fs_path = os.path.join(fm.root_dir, *rel_path_list)
47 self.assertEqual(path, fs_path)
47 self.assertEqual(path, fs_path)
48
48
49 fm = FileNotebookManager(notebook_dir=nbdir)
49 fm = FileContentsManager(root_dir=root)
50 path = fm._get_os_path('test.ipynb')
50 path = fm._get_os_path('test.ipynb')
51 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
51 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
52 self.assertEqual(path, fs_path)
52 self.assertEqual(path, fs_path)
53
53
54 fm = FileNotebookManager(notebook_dir=nbdir)
54 fm = FileContentsManager(root_dir=root)
55 path = fm._get_os_path('test.ipynb', '////')
55 path = fm._get_os_path('test.ipynb', '////')
56 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
56 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
57 self.assertEqual(path, fs_path)
57 self.assertEqual(path, fs_path)
58
58
59 def test_checkpoint_subdir(self):
59 def test_checkpoint_subdir(self):
60 subd = u'sub ∂ir'
60 subd = u'sub ∂ir'
61 cp_name = 'test-cp.ipynb'
61 cp_name = 'test-cp.ipynb'
62 with TemporaryDirectory() as td:
62 with TemporaryDirectory() as td:
63 nbdir = td
63 root = td
64 os.mkdir(os.path.join(td, subd))
64 os.mkdir(os.path.join(td, subd))
65 fm = FileNotebookManager(notebook_dir=nbdir)
65 fm = FileContentsManager(root_dir=root)
66 cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/')
66 cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/')
67 cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd)
67 cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd)
68 self.assertNotEqual(cp_dir, cp_subdir)
68 self.assertNotEqual(cp_dir, cp_subdir)
69 self.assertEqual(cp_dir, os.path.join(nbdir, fm.checkpoint_dir, cp_name))
69 self.assertEqual(cp_dir, os.path.join(root, fm.checkpoint_dir, cp_name))
70 self.assertEqual(cp_subdir, os.path.join(nbdir, subd, fm.checkpoint_dir, cp_name))
70 self.assertEqual(cp_subdir, os.path.join(root, subd, fm.checkpoint_dir, cp_name))
71
71
72
73 class TestContentsManager(TestCase):
72
74
73 class TestNotebookManager(TestCase):
74
75 def setUp(self):
75 def setUp(self):
76 self._temp_dir = TemporaryDirectory()
76 self._temp_dir = TemporaryDirectory()
77 self.td = self._temp_dir.name
77 self.td = self._temp_dir.name
78 self.notebook_manager = FileNotebookManager(
78 self.contents_manager = FileContentsManager(
79 notebook_dir=self.td,
79 root_dir=self.td,
80 log=logging.getLogger()
80 log=logging.getLogger()
81 )
81 )
82
82
83 def tearDown(self):
83 def tearDown(self):
84 self._temp_dir.cleanup()
84 self._temp_dir.cleanup()
85
85
86 def make_dir(self, abs_path, rel_path):
86 def make_dir(self, abs_path, rel_path):
87 """make subdirectory, rel_path is the relative path
87 """make subdirectory, rel_path is the relative path
88 to that directory from the location where the server started"""
88 to that directory from the location where the server started"""
@@ -91,31 +91,31 b' class TestNotebookManager(TestCase):'
91 os.makedirs(os_path)
91 os.makedirs(os_path)
92 except OSError:
92 except OSError:
93 print("Directory already exists: %r" % os_path)
93 print("Directory already exists: %r" % os_path)
94
94
95 def add_code_cell(self, nb):
95 def add_code_cell(self, nb):
96 output = current.new_output("display_data", output_javascript="alert('hi');")
96 output = current.new_output("display_data", output_javascript="alert('hi');")
97 cell = current.new_code_cell("print('hi')", outputs=[output])
97 cell = current.new_code_cell("print('hi')", outputs=[output])
98 if not nb.worksheets:
98 if not nb.worksheets:
99 nb.worksheets.append(current.new_worksheet())
99 nb.worksheets.append(current.new_worksheet())
100 nb.worksheets[0].cells.append(cell)
100 nb.worksheets[0].cells.append(cell)
101
101
102 def new_notebook(self):
102 def new_notebook(self):
103 nbm = self.notebook_manager
103 cm = self.contents_manager
104 model = nbm.create_notebook()
104 model = cm.create_file()
105 name = model['name']
105 name = model['name']
106 path = model['path']
106 path = model['path']
107
107
108 full_model = nbm.get_notebook(name, path)
108 full_model = cm.get_model(name, path)
109 nb = full_model['content']
109 nb = full_model['content']
110 self.add_code_cell(nb)
110 self.add_code_cell(nb)
111
111
112 nbm.save_notebook(full_model, name, path)
112 cm.save(full_model, name, path)
113 return nb, name, path
113 return nb, name, path
114
114
115 def test_create_notebook(self):
115 def test_create_file(self):
116 nm = self.notebook_manager
116 cm = self.contents_manager
117 # Test in root directory
117 # Test in root directory
118 model = nm.create_notebook()
118 model = cm.create_file()
119 assert isinstance(model, dict)
119 assert isinstance(model, dict)
120 self.assertIn('name', model)
120 self.assertIn('name', model)
121 self.assertIn('path', model)
121 self.assertIn('path', model)
@@ -124,23 +124,23 b' class TestNotebookManager(TestCase):'
124
124
125 # Test in sub-directory
125 # Test in sub-directory
126 sub_dir = '/foo/'
126 sub_dir = '/foo/'
127 self.make_dir(nm.notebook_dir, 'foo')
127 self.make_dir(cm.root_dir, 'foo')
128 model = nm.create_notebook(None, sub_dir)
128 model = cm.create_file(None, sub_dir)
129 assert isinstance(model, dict)
129 assert isinstance(model, dict)
130 self.assertIn('name', model)
130 self.assertIn('name', model)
131 self.assertIn('path', model)
131 self.assertIn('path', model)
132 self.assertEqual(model['name'], 'Untitled0.ipynb')
132 self.assertEqual(model['name'], 'Untitled0.ipynb')
133 self.assertEqual(model['path'], sub_dir.strip('/'))
133 self.assertEqual(model['path'], sub_dir.strip('/'))
134
134
135 def test_get_notebook(self):
135 def test_get(self):
136 nm = self.notebook_manager
136 cm = self.contents_manager
137 # Create a notebook
137 # Create a notebook
138 model = nm.create_notebook()
138 model = cm.create_file()
139 name = model['name']
139 name = model['name']
140 path = model['path']
140 path = model['path']
141
141
142 # Check that we 'get' on the notebook we just created
142 # Check that we 'get' on the notebook we just created
143 model2 = nm.get_notebook(name, path)
143 model2 = cm.get_model(name, path)
144 assert isinstance(model2, dict)
144 assert isinstance(model2, dict)
145 self.assertIn('name', model2)
145 self.assertIn('name', model2)
146 self.assertIn('path', model2)
146 self.assertIn('path', model2)
@@ -149,66 +149,66 b' class TestNotebookManager(TestCase):'
149
149
150 # Test in sub-directory
150 # Test in sub-directory
151 sub_dir = '/foo/'
151 sub_dir = '/foo/'
152 self.make_dir(nm.notebook_dir, 'foo')
152 self.make_dir(cm.root_dir, 'foo')
153 model = nm.create_notebook(None, sub_dir)
153 model = cm.create_file(None, sub_dir)
154 model2 = nm.get_notebook(name, sub_dir)
154 model2 = cm.get_model(name, sub_dir)
155 assert isinstance(model2, dict)
155 assert isinstance(model2, dict)
156 self.assertIn('name', model2)
156 self.assertIn('name', model2)
157 self.assertIn('path', model2)
157 self.assertIn('path', model2)
158 self.assertIn('content', model2)
158 self.assertIn('content', model2)
159 self.assertEqual(model2['name'], 'Untitled0.ipynb')
159 self.assertEqual(model2['name'], 'Untitled0.ipynb')
160 self.assertEqual(model2['path'], sub_dir.strip('/'))
160 self.assertEqual(model2['path'], sub_dir.strip('/'))
161
161
162 def test_update_notebook(self):
162 def test_update(self):
163 nm = self.notebook_manager
163 cm = self.contents_manager
164 # Create a notebook
164 # Create a notebook
165 model = nm.create_notebook()
165 model = cm.create_file()
166 name = model['name']
166 name = model['name']
167 path = model['path']
167 path = model['path']
168
168
169 # Change the name in the model for rename
169 # Change the name in the model for rename
170 model['name'] = 'test.ipynb'
170 model['name'] = 'test.ipynb'
171 model = nm.update_notebook(model, name, path)
171 model = cm.update(model, name, path)
172 assert isinstance(model, dict)
172 assert isinstance(model, dict)
173 self.assertIn('name', model)
173 self.assertIn('name', model)
174 self.assertIn('path', model)
174 self.assertIn('path', model)
175 self.assertEqual(model['name'], 'test.ipynb')
175 self.assertEqual(model['name'], 'test.ipynb')
176
176
177 # Make sure the old name is gone
177 # Make sure the old name is gone
178 self.assertRaises(HTTPError, nm.get_notebook, name, path)
178 self.assertRaises(HTTPError, cm.get_model, name, path)
179
179
180 # Test in sub-directory
180 # Test in sub-directory
181 # Create a directory and notebook in that directory
181 # Create a directory and notebook in that directory
182 sub_dir = '/foo/'
182 sub_dir = '/foo/'
183 self.make_dir(nm.notebook_dir, 'foo')
183 self.make_dir(cm.root_dir, 'foo')
184 model = nm.create_notebook(None, sub_dir)
184 model = cm.create_file(None, sub_dir)
185 name = model['name']
185 name = model['name']
186 path = model['path']
186 path = model['path']
187
187
188 # Change the name in the model for rename
188 # Change the name in the model for rename
189 model['name'] = 'test_in_sub.ipynb'
189 model['name'] = 'test_in_sub.ipynb'
190 model = nm.update_notebook(model, name, path)
190 model = cm.update(model, name, path)
191 assert isinstance(model, dict)
191 assert isinstance(model, dict)
192 self.assertIn('name', model)
192 self.assertIn('name', model)
193 self.assertIn('path', model)
193 self.assertIn('path', model)
194 self.assertEqual(model['name'], 'test_in_sub.ipynb')
194 self.assertEqual(model['name'], 'test_in_sub.ipynb')
195 self.assertEqual(model['path'], sub_dir.strip('/'))
195 self.assertEqual(model['path'], sub_dir.strip('/'))
196
196
197 # Make sure the old name is gone
197 # Make sure the old name is gone
198 self.assertRaises(HTTPError, nm.get_notebook, name, path)
198 self.assertRaises(HTTPError, cm.get_model, name, path)
199
199
200 def test_save_notebook(self):
200 def test_save(self):
201 nm = self.notebook_manager
201 cm = self.contents_manager
202 # Create a notebook
202 # Create a notebook
203 model = nm.create_notebook()
203 model = cm.create_file()
204 name = model['name']
204 name = model['name']
205 path = model['path']
205 path = model['path']
206
206
207 # Get the model with 'content'
207 # Get the model with 'content'
208 full_model = nm.get_notebook(name, path)
208 full_model = cm.get_model(name, path)
209
209
210 # Save the notebook
210 # Save the notebook
211 model = nm.save_notebook(full_model, name, path)
211 model = cm.save(full_model, name, path)
212 assert isinstance(model, dict)
212 assert isinstance(model, dict)
213 self.assertIn('name', model)
213 self.assertIn('name', model)
214 self.assertIn('path', model)
214 self.assertIn('path', model)
@@ -218,103 +218,84 b' class TestNotebookManager(TestCase):'
218 # Test in sub-directory
218 # Test in sub-directory
219 # Create a directory and notebook in that directory
219 # Create a directory and notebook in that directory
220 sub_dir = '/foo/'
220 sub_dir = '/foo/'
221 self.make_dir(nm.notebook_dir, 'foo')
221 self.make_dir(cm.root_dir, 'foo')
222 model = nm.create_notebook(None, sub_dir)
222 model = cm.create_file(None, sub_dir)
223 name = model['name']
223 name = model['name']
224 path = model['path']
224 path = model['path']
225 model = nm.get_notebook(name, path)
225 model = cm.get_model(name, path)
226
226
227 # Change the name in the model for rename
227 # Change the name in the model for rename
228 model = nm.save_notebook(model, name, path)
228 model = cm.save(model, name, path)
229 assert isinstance(model, dict)
229 assert isinstance(model, dict)
230 self.assertIn('name', model)
230 self.assertIn('name', model)
231 self.assertIn('path', model)
231 self.assertIn('path', model)
232 self.assertEqual(model['name'], 'Untitled0.ipynb')
232 self.assertEqual(model['name'], 'Untitled0.ipynb')
233 self.assertEqual(model['path'], sub_dir.strip('/'))
233 self.assertEqual(model['path'], sub_dir.strip('/'))
234
234
235 def test_save_notebook_with_script(self):
235 def test_delete(self):
236 nm = self.notebook_manager
236 cm = self.contents_manager
237 # Create a notebook
238 model = nm.create_notebook()
239 nm.save_script = True
240 model = nm.create_notebook()
241 name = model['name']
242 path = model['path']
243
244 # Get the model with 'content'
245 full_model = nm.get_notebook(name, path)
246
247 # Save the notebook
248 model = nm.save_notebook(full_model, name, path)
249
250 # Check that the script was created
251 py_path = os.path.join(nm.notebook_dir, os.path.splitext(name)[0]+'.py')
252 assert os.path.exists(py_path), py_path
253
254 def test_delete_notebook(self):
255 nm = self.notebook_manager
256 # Create a notebook
237 # Create a notebook
257 nb, name, path = self.new_notebook()
238 nb, name, path = self.new_notebook()
258
239
259 # Delete the notebook
240 # Delete the notebook
260 nm.delete_notebook(name, path)
241 cm.delete(name, path)
261
242
262 # Check that a 'get' on the deleted notebook raises and error
243 # Check that a 'get' on the deleted notebook raises and error
263 self.assertRaises(HTTPError, nm.get_notebook, name, path)
244 self.assertRaises(HTTPError, cm.get_model, name, path)
264
245
265 def test_copy_notebook(self):
246 def test_copy(self):
266 nm = self.notebook_manager
247 cm = self.contents_manager
267 path = u'å b'
248 path = u'å b'
268 name = u'nb √.ipynb'
249 name = u'nb √.ipynb'
269 os.mkdir(os.path.join(nm.notebook_dir, path))
250 os.mkdir(os.path.join(cm.root_dir, path))
270 orig = nm.create_notebook({'name' : name}, path=path)
251 orig = cm.create_file({'name' : name}, path=path)
271
252
272 # copy with unspecified name
253 # copy with unspecified name
273 copy = nm.copy_notebook(name, path=path)
254 copy = cm.copy(name, path=path)
274 self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb'))
255 self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb'))
275
256
276 # copy with specified name
257 # copy with specified name
277 copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path)
258 copy2 = cm.copy(name, u'copy 2.ipynb', path=path)
278 self.assertEqual(copy2['name'], u'copy 2.ipynb')
259 self.assertEqual(copy2['name'], u'copy 2.ipynb')
279
260
280 def test_trust_notebook(self):
261 def test_trust_notebook(self):
281 nbm = self.notebook_manager
262 cm = self.contents_manager
282 nb, name, path = self.new_notebook()
263 nb, name, path = self.new_notebook()
283
264
284 untrusted = nbm.get_notebook(name, path)['content']
265 untrusted = cm.get_model(name, path)['content']
285 assert not nbm.notary.check_cells(untrusted)
266 assert not cm.notary.check_cells(untrusted)
286
267
287 # print(untrusted)
268 # print(untrusted)
288 nbm.trust_notebook(name, path)
269 cm.trust_notebook(name, path)
289 trusted = nbm.get_notebook(name, path)['content']
270 trusted = cm.get_model(name, path)['content']
290 # print(trusted)
271 # print(trusted)
291 assert nbm.notary.check_cells(trusted)
272 assert cm.notary.check_cells(trusted)
292
273
293 def test_mark_trusted_cells(self):
274 def test_mark_trusted_cells(self):
294 nbm = self.notebook_manager
275 cm = self.contents_manager
295 nb, name, path = self.new_notebook()
276 nb, name, path = self.new_notebook()
296
277
297 nbm.mark_trusted_cells(nb, name, path)
278 cm.mark_trusted_cells(nb, name, path)
298 for cell in nb.worksheets[0].cells:
279 for cell in nb.worksheets[0].cells:
299 if cell.cell_type == 'code':
280 if cell.cell_type == 'code':
300 assert not cell.trusted
281 assert not cell.trusted
301
282
302 nbm.trust_notebook(name, path)
283 cm.trust_notebook(name, path)
303 nb = nbm.get_notebook(name, path)['content']
284 nb = cm.get_model(name, path)['content']
304 for cell in nb.worksheets[0].cells:
285 for cell in nb.worksheets[0].cells:
305 if cell.cell_type == 'code':
286 if cell.cell_type == 'code':
306 assert cell.trusted
287 assert cell.trusted
307
288
308 def test_check_and_sign(self):
289 def test_check_and_sign(self):
309 nbm = self.notebook_manager
290 cm = self.contents_manager
310 nb, name, path = self.new_notebook()
291 nb, name, path = self.new_notebook()
311
292
312 nbm.mark_trusted_cells(nb, name, path)
293 cm.mark_trusted_cells(nb, name, path)
313 nbm.check_and_sign(nb, name, path)
294 cm.check_and_sign(nb, name, path)
314 assert not nbm.notary.check_signature(nb)
295 assert not cm.notary.check_signature(nb)
315
296
316 nbm.trust_notebook(name, path)
297 cm.trust_notebook(name, path)
317 nb = nbm.get_notebook(name, path)['content']
298 nb = cm.get_model(name, path)['content']
318 nbm.mark_trusted_cells(nb, name, path)
299 cm.mark_trusted_cells(nb, name, path)
319 nbm.check_and_sign(nb, name, path)
300 cm.check_and_sign(nb, name, path)
320 assert nbm.notary.check_signature(nb)
301 assert cm.notary.check_signature(nb)
@@ -27,8 +27,16 b' class MainKernelHandler(IPythonHandler):'
27 @web.authenticated
27 @web.authenticated
28 @json_errors
28 @json_errors
29 def post(self):
29 def post(self):
30 model = self.get_json_body()
31 if model is None:
32 raise web.HTTPError(400, "No JSON data provided")
33 try:
34 name = model['name']
35 except KeyError:
36 raise web.HTTPError(400, "Missing field in JSON data: name")
37
30 km = self.kernel_manager
38 km = self.kernel_manager
31 kernel_id = km.start_kernel()
39 kernel_id = km.start_kernel(kernel_name=name)
32 model = km.kernel_model(kernel_id)
40 model = km.kernel_model(kernel_id)
33 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
41 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
34 self.set_header('Location', url_escape(location))
42 self.set_header('Location', url_escape(location))
@@ -76,6 +84,9 b' class KernelActionHandler(IPythonHandler):'
76
84
77 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
85 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
78
86
87 def __repr__(self):
88 return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized'))
89
79 def create_stream(self):
90 def create_stream(self):
80 km = self.kernel_manager
91 km = self.kernel_manager
81 meth = getattr(km, 'connect_%s' % self.channel)
92 meth = getattr(km, 'connect_%s' % self.channel)
@@ -137,6 +148,12 b' class ZMQChannelHandler(AuthenticatedZMQStreamHandler):'
137 self.zmq_stream.on_recv(self._on_zmq_reply)
148 self.zmq_stream.on_recv(self._on_zmq_reply)
138
149
139 def on_message(self, msg):
150 def on_message(self, msg):
151 if self.zmq_stream is None:
152 return
153 elif self.zmq_stream.closed():
154 self.log.info("%s closed, closing websocket.", self)
155 self.close()
156 return
140 msg = json.loads(msg)
157 msg = json.loads(msg)
141 self.session.send(self.zmq_stream, msg)
158 self.session.send(self.zmq_stream, msg)
142
159
@@ -72,8 +72,8 b' class MappingKernelManager(MultiKernelManager):'
72 os_path = os.path.dirname(os_path)
72 os_path = os.path.dirname(os_path)
73 return os_path
73 return os_path
74
74
75 def start_kernel(self, kernel_id=None, path=None, **kwargs):
75 def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
76 """Start a kernel for a session an return its kernel_id.
76 """Start a kernel for a session and return its kernel_id.
77
77
78 Parameters
78 Parameters
79 ----------
79 ----------
@@ -84,12 +84,16 b' class MappingKernelManager(MultiKernelManager):'
84 path : API path
84 path : API path
85 The API path (unicode, '/' delimited) for the cwd.
85 The API path (unicode, '/' delimited) for the cwd.
86 Will be transformed to an OS path relative to root_dir.
86 Will be transformed to an OS path relative to root_dir.
87 kernel_name : str
88 The name identifying which kernel spec to launch. This is ignored if
89 an existing kernel is returned, but it may be checked in the future.
87 """
90 """
88 if kernel_id is None:
91 if kernel_id is None:
89 kwargs['extra_arguments'] = self.kernel_argv
92 kwargs['extra_arguments'] = self.kernel_argv
90 if path is not None:
93 if path is not None:
91 kwargs['cwd'] = self.cwd_for_path(path)
94 kwargs['cwd'] = self.cwd_for_path(path)
92 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
95 kernel_id = super(MappingKernelManager, self).start_kernel(
96 kernel_name=kernel_name, **kwargs)
93 self.log.info("Kernel started: %s" % kernel_id)
97 self.log.info("Kernel started: %s" % kernel_id)
94 self.log.debug("Kernel args: %r" % kwargs)
98 self.log.debug("Kernel args: %r" % kwargs)
95 # register callback for failed auto-restart
99 # register callback for failed auto-restart
@@ -111,7 +115,8 b' class MappingKernelManager(MultiKernelManager):'
111 """Return a dictionary of kernel information described in the
115 """Return a dictionary of kernel information described in the
112 JSON standard model."""
116 JSON standard model."""
113 self._check_kernel_id(kernel_id)
117 self._check_kernel_id(kernel_id)
114 model = {"id":kernel_id}
118 model = {"id":kernel_id,
119 "name": self._kernels[kernel_id].kernel_name}
115 return model
120 return model
116
121
117 def list_kernels(self):
122 def list_kernels(self):
@@ -1,6 +1,6 b''
1 """Test the kernels service API."""
1 """Test the kernels service API."""
2
2
3
3 import json
4 import requests
4 import requests
5
5
6 from IPython.html.utils import url_path_join
6 from IPython.html.utils import url_path_join
@@ -30,8 +30,9 b' class KernelAPI(object):'
30 def get(self, id):
30 def get(self, id):
31 return self._req('GET', id)
31 return self._req('GET', id)
32
32
33 def start(self):
33 def start(self, name='python'):
34 return self._req('POST', '')
34 body = json.dumps({'name': name})
35 return self._req('POST', '', body)
35
36
36 def shutdown(self, id):
37 def shutdown(self, id):
37 return self._req('DELETE', id)
38 return self._req('DELETE', id)
@@ -64,11 +65,14 b' class KernelAPITest(NotebookTestBase):'
64 self.assertEqual(r.status_code, 201)
65 self.assertEqual(r.status_code, 201)
65 self.assertIsInstance(kern1, dict)
66 self.assertIsInstance(kern1, dict)
66
67
68 self.assertEqual(r.headers['x-frame-options'], "SAMEORIGIN")
69
67 # GET request
70 # GET request
68 r = self.kern_api.list()
71 r = self.kern_api.list()
69 self.assertEqual(r.status_code, 200)
72 self.assertEqual(r.status_code, 200)
70 assert isinstance(r.json(), list)
73 assert isinstance(r.json(), list)
71 self.assertEqual(r.json()[0]['id'], kern1['id'])
74 self.assertEqual(r.json()[0]['id'], kern1['id'])
75 self.assertEqual(r.json()[0]['name'], kern1['name'])
72
76
73 # create another kernel and check that they both are added to the
77 # create another kernel and check that they both are added to the
74 # list of kernels from a GET request
78 # list of kernels from a GET request
@@ -89,6 +93,7 b' class KernelAPITest(NotebookTestBase):'
89 self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id'])
93 self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id'])
90 rekern = r.json()
94 rekern = r.json()
91 self.assertEqual(rekern['id'], kern2['id'])
95 self.assertEqual(rekern['id'], kern2['id'])
96 self.assertEqual(rekern['name'], kern2['name'])
92
97
93 def test_kernel_handler(self):
98 def test_kernel_handler(self):
94 # GET kernel with given id
99 # GET kernel with given id
@@ -7,6 +7,8 b' from tornado import web'
7
7
8 from ...base.handlers import IPythonHandler, json_errors
8 from ...base.handlers import IPythonHandler, json_errors
9
9
10 from IPython.kernel.kernelspec import _pythonfirst
11
10
12
11 class MainKernelSpecHandler(IPythonHandler):
13 class MainKernelSpecHandler(IPythonHandler):
12 SUPPORTED_METHODS = ('GET',)
14 SUPPORTED_METHODS = ('GET',)
@@ -16,7 +18,7 b' class MainKernelSpecHandler(IPythonHandler):'
16 def get(self):
18 def get(self):
17 ksm = self.kernel_spec_manager
19 ksm = self.kernel_spec_manager
18 results = []
20 results = []
19 for kernel_name in ksm.find_kernel_specs():
21 for kernel_name in sorted(ksm.find_kernel_specs(), key=_pythonfirst):
20 d = ksm.get_kernel_spec(kernel_name).to_dict()
22 d = ksm.get_kernel_spec(kernel_name).to_dict()
21 d['name'] = kernel_name
23 d['name'] = kernel_name
22 results.append(d)
24 results.append(d)
@@ -1,20 +1,7 b''
1 """Tornado handlers for the sessions web service.
1 """Tornado handlers for the sessions web service."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Zach Sailer
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 import json
6 import json
20
7
@@ -24,10 +11,6 b' from ...base.handlers import IPythonHandler, json_errors'
24 from IPython.utils.jsonutil import date_default
11 from IPython.utils.jsonutil import date_default
25 from IPython.html.utils import url_path_join, url_escape
12 from IPython.html.utils import url_path_join, url_escape
26
13
27 #-----------------------------------------------------------------------------
28 # Session web service handlers
29 #-----------------------------------------------------------------------------
30
31
14
32 class SessionRootHandler(IPythonHandler):
15 class SessionRootHandler(IPythonHandler):
33
16
@@ -45,27 +28,30 b' class SessionRootHandler(IPythonHandler):'
45 # Creates a new session
28 # Creates a new session
46 #(unless a session already exists for the named nb)
29 #(unless a session already exists for the named nb)
47 sm = self.session_manager
30 sm = self.session_manager
48 nbm = self.notebook_manager
31 cm = self.contents_manager
49 km = self.kernel_manager
32 km = self.kernel_manager
33
50 model = self.get_json_body()
34 model = self.get_json_body()
51 if model is None:
35 if model is None:
52 raise web.HTTPError(400, "No JSON data provided")
36 raise web.HTTPError(400, "No JSON data provided")
53 try:
37 try:
54 name = model['notebook']['name']
38 name = model['notebook']['name']
55 except KeyError:
39 except KeyError:
56 raise web.HTTPError(400, "Missing field in JSON data: name")
40 raise web.HTTPError(400, "Missing field in JSON data: notebook.name")
57 try:
41 try:
58 path = model['notebook']['path']
42 path = model['notebook']['path']
59 except KeyError:
43 except KeyError:
60 raise web.HTTPError(400, "Missing field in JSON data: path")
44 raise web.HTTPError(400, "Missing field in JSON data: notebook.path")
45 try:
46 kernel_name = model['kernel']['name']
47 except KeyError:
48 raise web.HTTPError(400, "Missing field in JSON data: kernel.name")
49
61 # Check to see if session exists
50 # Check to see if session exists
62 if sm.session_exists(name=name, path=path):
51 if sm.session_exists(name=name, path=path):
63 model = sm.get_session(name=name, path=path)
52 model = sm.get_session(name=name, path=path)
64 else:
53 else:
65 # allow nbm to specify kernels cwd
54 model = sm.create_session(name=name, path=path, kernel_name=kernel_name)
66 kernel_path = nbm.get_kernel_path(name=name, path=path)
67 kernel_id = km.start_kernel(path=kernel_path)
68 model = sm.create_session(name=name, path=path, kernel_id=kernel_id)
69 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
55 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
70 self.set_header('Location', url_escape(location))
56 self.set_header('Location', url_escape(location))
71 self.set_status(201)
57 self.set_status(201)
@@ -108,10 +94,7 b' class SessionHandler(IPythonHandler):'
108 def delete(self, session_id):
94 def delete(self, session_id):
109 # Deletes the session with given session_id
95 # Deletes the session with given session_id
110 sm = self.session_manager
96 sm = self.session_manager
111 km = self.kernel_manager
112 session = sm.get_session(session_id=session_id)
113 sm.delete_session(session_id)
97 sm.delete_session(session_id)
114 km.shutdown_kernel(session['kernel']['id'])
115 self.set_status(204)
98 self.set_status(204)
116 self.finish()
99 self.finish()
117
100
@@ -23,12 +23,16 b' from tornado import web'
23
23
24 from IPython.config.configurable import LoggingConfigurable
24 from IPython.config.configurable import LoggingConfigurable
25 from IPython.utils.py3compat import unicode_type
25 from IPython.utils.py3compat import unicode_type
26 from IPython.utils.traitlets import Instance
26
27
27 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
28 # Classes
29 # Classes
29 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
30
31
31 class SessionManager(LoggingConfigurable):
32 class SessionManager(LoggingConfigurable):
33
34 kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
35 contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=())
32
36
33 # Session database initialized below
37 # Session database initialized below
34 _cursor = None
38 _cursor = None
@@ -69,10 +73,15 b' class SessionManager(LoggingConfigurable):'
69 "Create a uuid for a new session"
73 "Create a uuid for a new session"
70 return unicode_type(uuid.uuid4())
74 return unicode_type(uuid.uuid4())
71
75
72 def create_session(self, name=None, path=None, kernel_id=None):
76 def create_session(self, name=None, path=None, kernel_name='python'):
73 """Creates a session and returns its model"""
77 """Creates a session and returns its model"""
74 session_id = self.new_session_id()
78 session_id = self.new_session_id()
75 return self.save_session(session_id, name=name, path=path, kernel_id=kernel_id)
79 # allow nbm to specify kernels cwd
80 kernel_path = self.contents_manager.get_kernel_path(name=name, path=path)
81 kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
82 kernel_name=kernel_name)
83 return self.save_session(session_id, name=name, path=path,
84 kernel_id=kernel_id)
76
85
77 def save_session(self, session_id, name=None, path=None, kernel_id=None):
86 def save_session(self, session_id, name=None, path=None, kernel_id=None):
78 """Saves the items for the session with the given session_id
87 """Saves the items for the session with the given session_id
@@ -170,8 +179,7 b' class SessionManager(LoggingConfigurable):'
170 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
179 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
171 self.cursor.execute(query, list(kwargs.values()) + [session_id])
180 self.cursor.execute(query, list(kwargs.values()) + [session_id])
172
181
173 @staticmethod
182 def row_factory(self, cursor, row):
174 def row_factory(cursor, row):
175 """Takes sqlite database session row and turns it into a dictionary"""
183 """Takes sqlite database session row and turns it into a dictionary"""
176 row = sqlite3.Row(cursor, row)
184 row = sqlite3.Row(cursor, row)
177 model = {
185 model = {
@@ -180,9 +188,7 b' class SessionManager(LoggingConfigurable):'
180 'name': row['name'],
188 'name': row['name'],
181 'path': row['path']
189 'path': row['path']
182 },
190 },
183 'kernel': {
191 'kernel': self.kernel_manager.kernel_model(row['kernel_id'])
184 'id': row['kernel_id'],
185 }
186 }
192 }
187 return model
193 return model
188
194
@@ -195,5 +201,6 b' class SessionManager(LoggingConfigurable):'
195 def delete_session(self, session_id):
201 def delete_session(self, session_id):
196 """Deletes the row in the session database with given session_id"""
202 """Deletes the row in the session database with given session_id"""
197 # Check that session exists before deleting
203 # Check that session exists before deleting
198 self.get_session(session_id=session_id)
204 session = self.get_session(session_id=session_id)
205 self.kernel_manager.shutdown_kernel(session['kernel']['id'])
199 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
206 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
@@ -5,79 +5,101 b' from unittest import TestCase'
5 from tornado import web
5 from tornado import web
6
6
7 from ..sessionmanager import SessionManager
7 from ..sessionmanager import SessionManager
8 from IPython.html.services.kernels.kernelmanager import MappingKernelManager
9
10 class DummyKernel(object):
11 def __init__(self, kernel_name='python'):
12 self.kernel_name = kernel_name
13
14 class DummyMKM(MappingKernelManager):
15 """MappingKernelManager interface that doesn't start kernels, for testing"""
16 def __init__(self, *args, **kwargs):
17 super(DummyMKM, self).__init__(*args, **kwargs)
18 self.id_letters = iter(u'ABCDEFGHIJK')
19
20 def _new_id(self):
21 return next(self.id_letters)
22
23 def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
24 kernel_id = kernel_id or self._new_id()
25 self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name)
26 return kernel_id
27
28 def shutdown_kernel(self, kernel_id, now=False):
29 del self._kernels[kernel_id]
8
30
9 class TestSessionManager(TestCase):
31 class TestSessionManager(TestCase):
10
32
11 def test_get_session(self):
33 def test_get_session(self):
12 sm = SessionManager()
34 sm = SessionManager(kernel_manager=DummyMKM())
13 session_id = sm.new_session_id()
35 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
14 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
36 kernel_name='bar')['id']
15 model = sm.get_session(session_id=session_id)
37 model = sm.get_session(session_id=session_id)
16 expected = {'id':session_id, 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}}
38 expected = {'id':session_id,
39 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'},
40 'kernel': {'id':u'A', 'name': 'bar'}}
17 self.assertEqual(model, expected)
41 self.assertEqual(model, expected)
18
42
19 def test_bad_get_session(self):
43 def test_bad_get_session(self):
20 # Should raise error if a bad key is passed to the database.
44 # Should raise error if a bad key is passed to the database.
21 sm = SessionManager()
45 sm = SessionManager(kernel_manager=DummyMKM())
22 session_id = sm.new_session_id()
46 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
23 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
47 kernel_name='foo')['id']
24 self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword
48 self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword
25
49
26 def test_list_sessions(self):
50 def test_list_sessions(self):
27 sm = SessionManager()
51 sm = SessionManager(kernel_manager=DummyMKM())
28 session_id1 = sm.new_session_id()
52 sessions = [
29 session_id2 = sm.new_session_id()
53 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
30 session_id3 = sm.new_session_id()
54 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
31 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678')
55 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
32 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678')
56 ]
33 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678')
34 sessions = sm.list_sessions()
57 sessions = sm.list_sessions()
35 expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
58 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
36 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}},
59 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
37 {'id':session_id2, 'notebook': {'name':u'test2.ipynb',
60 {'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb',
38 'path': u'/path/to/2/'}, 'kernel':{'id':u'5678'}},
61 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}},
39 {'id':session_id3, 'notebook':{'name':u'test3.ipynb',
62 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
40 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}]
63 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
41 self.assertEqual(sessions, expected)
64 self.assertEqual(sessions, expected)
42
65
43 def test_update_session(self):
66 def test_update_session(self):
44 sm = SessionManager()
67 sm = SessionManager(kernel_manager=DummyMKM())
45 session_id = sm.new_session_id()
68 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
46 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id=None)
69 kernel_name='julia')['id']
47 sm.update_session(session_id, kernel_id='5678')
48 sm.update_session(session_id, name='new_name.ipynb')
70 sm.update_session(session_id, name='new_name.ipynb')
49 model = sm.get_session(session_id=session_id)
71 model = sm.get_session(session_id=session_id)
50 expected = {'id':session_id, 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}}
72 expected = {'id':session_id,
73 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'},
74 'kernel':{'id':u'A', 'name':'julia'}}
51 self.assertEqual(model, expected)
75 self.assertEqual(model, expected)
52
76
53 def test_bad_update_session(self):
77 def test_bad_update_session(self):
54 # try to update a session with a bad keyword ~ raise error
78 # try to update a session with a bad keyword ~ raise error
55 sm = SessionManager()
79 sm = SessionManager(kernel_manager=DummyMKM())
56 session_id = sm.new_session_id()
80 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
57 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
81 kernel_name='ir')['id']
58 self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
82 self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
59
83
60 def test_delete_session(self):
84 def test_delete_session(self):
61 sm = SessionManager()
85 sm = SessionManager(kernel_manager=DummyMKM())
62 session_id1 = sm.new_session_id()
86 sessions = [
63 session_id2 = sm.new_session_id()
87 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
64 session_id3 = sm.new_session_id()
88 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
65 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678')
89 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
66 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678')
90 ]
67 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678')
91 sm.delete_session(sessions[1]['id'])
68 sm.delete_session(session_id2)
92 new_sessions = sm.list_sessions()
69 sessions = sm.list_sessions()
93 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
70 expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
94 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
71 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}},
95 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
72 {'id':session_id3, 'notebook':{'name':u'test3.ipynb',
96 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
73 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}]
97 self.assertEqual(new_sessions, expected)
74 self.assertEqual(sessions, expected)
75
98
76 def test_bad_delete_session(self):
99 def test_bad_delete_session(self):
77 # try to delete a session that doesn't exist ~ raise error
100 # try to delete a session that doesn't exist ~ raise error
78 sm = SessionManager()
101 sm = SessionManager(kernel_manager=DummyMKM())
79 session_id = sm.new_session_id()
102 sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python')
80 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
81 self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
103 self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
82 self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
104 self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
83
105
@@ -37,8 +37,9 b' class SessionAPI(object):'
37 def get(self, id):
37 def get(self, id):
38 return self._req('GET', id)
38 return self._req('GET', id)
39
39
40 def create(self, name, path):
40 def create(self, name, path, kernel_name='python'):
41 body = json.dumps({'notebook': {'name':name, 'path':path}})
41 body = json.dumps({'notebook': {'name':name, 'path':path},
42 'kernel': {'name': kernel_name}})
42 return self._req('POST', '', body)
43 return self._req('POST', '', body)
43
44
44 def modify(self, id, name, path):
45 def modify(self, id, name, path):
@@ -1,21 +1,12 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 var ipython = ipython || {};
9 // On document ready
5 require(['base/js/page'], function(page) {
10 //============================================================================
6 var page_instance = new page.Page();
11
12
13 $(document).ready(function () {
14
15 IPython.page = new IPython.Page();
16 $('button#login_submit').addClass("btn btn-default");
7 $('button#login_submit').addClass("btn btn-default");
17 IPython.page.show();
8 page_instance.show();
18 $('input#password_input').focus();
9 $('input#password_input').focus();
19
10
11 ipython.page = page_instance;
20 });
12 });
21
@@ -1,43 +1,35 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'base/js/utils',
7
7 'jquery',
8 //============================================================================
8 ], function(IPython, utils, $){
9 // Login button
10 //============================================================================
11
12 var IPython = (function (IPython) {
13 "use strict";
9 "use strict";
14
10
15 var LoginWidget = function (selector, options) {
11 var LoginWidget = function (selector, options) {
16 options = options || {};
12 options = options || {};
17 this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl");
13 this.base_url = options.base_url || utils.get_body_data("baseUrl");
18 this.selector = selector;
14 this.selector = selector;
19 if (this.selector !== undefined) {
15 if (this.selector !== undefined) {
20 this.element = $(selector);
16 this.element = $(selector);
21 this.style();
22 this.bind_events();
17 this.bind_events();
23 }
18 }
24 };
19 };
25
20
26 LoginWidget.prototype.style = function () {
27 this.element.find("button").addClass("btn btn-default btn-sm");
28 };
29
21
30
22
31 LoginWidget.prototype.bind_events = function () {
23 LoginWidget.prototype.bind_events = function () {
32 var that = this;
24 var that = this;
33 this.element.find("button#logout").click(function () {
25 this.element.find("button#logout").click(function () {
34 window.location = IPython.utils.url_join_encode(
26 window.location = utils.url_join_encode(
35 that.base_url,
27 that.base_url,
36 "logout"
28 "logout"
37 );
29 );
38 });
30 });
39 this.element.find("button#login").click(function () {
31 this.element.find("button#login").click(function () {
40 window.location = IPython.utils.url_join_encode(
32 window.location = utils.url_join_encode(
41 that.base_url,
33 that.base_url,
42 "login"
34 "login"
43 );
35 );
@@ -47,6 +39,5 b' var IPython = (function (IPython) {'
47 // Set module variables
39 // Set module variables
48 IPython.LoginWidget = LoginWidget;
40 IPython.LoginWidget = LoginWidget;
49
41
50 return IPython;
42 return {'LoginWidget': LoginWidget};
51
43 });
52 }(IPython));
@@ -1,20 +1,10 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 var ipython = ipython || {};
9 // On document ready
5 require(['base/js/page'], function(page) {
10 //============================================================================
6 var page_instance = new page.Page();
11
7 page_instance.show();
12
13 $(document).ready(function () {
14
15 IPython.page = new IPython.Page();
16 $('#ipython-main-app').addClass('border-box-sizing');
17 IPython.page.show();
18
8
9 ipython.page = page_instance;
19 });
10 });
20
@@ -1,2 +1,7 b''
1 /*!
2 *
3 * IPython auth
4 *
5 */
1 @import "login.less";
6 @import "login.less";
2 @import "logout.less"; No newline at end of file
7 @import "logout.less";
@@ -1,20 +1,14 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2013 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Utility for modal dialogs with bootstrap
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 ], function(IPython, $) {
12 IPython.namespace('IPython.dialog');
13
14 IPython.dialog = (function (IPython) {
15 "use strict";
8 "use strict";
16
9
17 var modal = function (options) {
10 var modal = function (options) {
11
18 var modal = $("<div/>")
12 var modal = $("<div/>")
19 .addClass("modal")
13 .addClass("modal")
20 .addClass("fade")
14 .addClass("fade")
@@ -79,26 +73,28 b' IPython.dialog = (function (IPython) {'
79 });
73 });
80 }
74 }
81 modal.on("hidden.bs.modal", function () {
75 modal.on("hidden.bs.modal", function () {
82 if (IPython.notebook) {
76 if (options.notebook) {
83 var cell = IPython.notebook.get_selected_cell();
77 var cell = options.notebook.get_selected_cell();
84 if (cell) cell.select();
78 if (cell) cell.select();
85 IPython.keyboard_manager.enable();
79 }
86 IPython.keyboard_manager.command_mode();
80 if (options.keyboard_manager) {
81 options.keyboard_manager.enable();
82 options.keyboard_manager.command_mode();
87 }
83 }
88 });
84 });
89
85
90 if (IPython.keyboard_manager) {
86 if (options.keyboard_manager) {
91 IPython.keyboard_manager.disable();
87 options.keyboard_manager.disable();
92 }
88 }
93
89
94 return modal.modal(options);
90 return modal.modal(options);
95 };
91 };
96
92
97 var edit_metadata = function (md, callback, name) {
93 var edit_metadata = function (options) {
98 name = name || "Cell";
94 options.name = options.name || "Cell";
99 var error_div = $('<div/>').css('color', 'red');
95 var error_div = $('<div/>').css('color', 'red');
100 var message =
96 var message =
101 "Manually edit the JSON below to manipulate the metadata for this " + name + "." +
97 "Manually edit the JSON below to manipulate the metadata for this " + options.name + "." +
102 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
98 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
103 " so they don't conflict with those of others.";
99 " so they don't conflict with those of others.";
104
100
@@ -106,7 +102,7 b' IPython.dialog = (function (IPython) {'
106 .attr('rows', '13')
102 .attr('rows', '13')
107 .attr('cols', '80')
103 .attr('cols', '80')
108 .attr('name', 'metadata')
104 .attr('name', 'metadata')
109 .text(JSON.stringify(md || {}, null, 2));
105 .text(JSON.stringify(options.md || {}, null, 2));
110
106
111 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
107 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
112 .append(
108 .append(
@@ -128,8 +124,8 b' IPython.dialog = (function (IPython) {'
128 autoIndent: true,
124 autoIndent: true,
129 mode: 'application/json',
125 mode: 'application/json',
130 });
126 });
131 var modal = IPython.dialog.modal({
127 var modal_obj = modal({
132 title: "Edit " + name + " Metadata",
128 title: "Edit " + options.name + " Metadata",
133 body: dialogform,
129 body: dialogform,
134 buttons: {
130 buttons: {
135 OK: { class : "btn-primary",
131 OK: { class : "btn-primary",
@@ -143,19 +139,25 b' IPython.dialog = (function (IPython) {'
143 error_div.text('WARNING: Could not save invalid JSON.');
139 error_div.text('WARNING: Could not save invalid JSON.');
144 return false;
140 return false;
145 }
141 }
146 callback(new_md);
142 options.callback(new_md);
147 }
143 }
148 },
144 },
149 Cancel: {}
145 Cancel: {}
150 }
146 },
147 notebook: options.notebook,
148 keyboard_manager: options.keyboard_manager,
151 });
149 });
152
150
153 modal.on('shown.bs.modal', function(){ editor.refresh(); });
151 modal_obj.on('shown.bs.modal', function(){ editor.refresh(); });
154 };
152 };
155
153
156 return {
154 var dialog = {
157 modal : modal,
155 modal : modal,
158 edit_metadata : edit_metadata,
156 edit_metadata : edit_metadata,
159 };
157 };
160
158
161 }(IPython));
159 // Backwards compatability.
160 IPython.dialog = dialog;
161
162 return dialog;
163 });
@@ -1,32 +1,24 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Events
10 //============================================================================
11
3
12 // Give us an object to bind all events to. This object should be created
4 // Give us an object to bind all events to. This object should be created
13 // before all other objects so it exists when others register event handlers.
5 // before all other objects so it exists when others register event handlers.
14 // To trigger an event handler:
6 // To register an event handler:
15 // $([IPython.events]).trigger('event.Namespace');
7 //
16 // To handle it:
8 // require(['base/js/events'], function (events) {
17 // $([IPython.events]).on('event.Namespace',function () {});
9 // events.on("event.Namespace", function () { do_stuff(); });
10 // });
18
11
19 var IPython = (function (IPython) {
12 define(['base/js/namespace', 'jquery'], function(IPython, $) {
20 "use strict";
13 "use strict";
21
14
22 var utils = IPython.utils;
23
24 var Events = function () {};
15 var Events = function () {};
25
16
17 var events = new Events();
18
19 // Backwards compatability.
26 IPython.Events = Events;
20 IPython.Events = Events;
27 IPython.events = new Events();
21 IPython.events = events;
28
22
29 return IPython;
23 return $([events]);
30
24 });
31 }(IPython));
32
@@ -1,19 +1,14 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Keyboard management
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 'base/js/utils',
12 IPython.namespace('IPython.keyboard');
8 ], function(IPython, $, utils) {
13
14 IPython.keyboard = (function (IPython) {
15 "use strict";
9 "use strict";
16
10
11
17 // Setup global keycodes and inverse keycodes.
12 // Setup global keycodes and inverse keycodes.
18
13
19 // See http://unixpapa.com/js/key.html for a complete description. The short of
14 // See http://unixpapa.com/js/key.html for a complete description. The short of
@@ -51,8 +46,8 b' IPython.keyboard = (function (IPython) {'
51 '; :': 186, '= +': 187, '- _': 189
46 '; :': 186, '= +': 187, '- _': 189
52 };
47 };
53
48
54 var browser = IPython.utils.browser[0];
49 var browser = utils.browser[0];
55 var platform = IPython.utils.platform;
50 var platform = utils.platform;
56
51
57 if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
52 if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
58 $.extend(_keycodes, _mozilla_keycodes);
53 $.extend(_keycodes, _mozilla_keycodes);
@@ -130,18 +125,19 b' IPython.keyboard = (function (IPython) {'
130
125
131 // Shortcut manager class
126 // Shortcut manager class
132
127
133 var ShortcutManager = function (delay) {
128 var ShortcutManager = function (delay, events) {
134 this._shortcuts = {};
129 this._shortcuts = {};
135 this._counts = {};
130 this._counts = {};
136 this._timers = {};
131 this._timers = {};
137 this.delay = delay || 800; // delay in milliseconds
132 this.delay = delay || 800; // delay in milliseconds
133 this.events = events;
138 };
134 };
139
135
140 ShortcutManager.prototype.help = function () {
136 ShortcutManager.prototype.help = function () {
141 var help = [];
137 var help = [];
142 for (var shortcut in this._shortcuts) {
138 for (var shortcut in this._shortcuts) {
143 var help_string = this._shortcuts[shortcut]['help'];
139 var help_string = this._shortcuts[shortcut].help;
144 var help_index = this._shortcuts[shortcut]['help_index'];
140 var help_index = this._shortcuts[shortcut].help_index;
145 if (help_string) {
141 if (help_string) {
146 if (platform === 'MacOS') {
142 if (platform === 'MacOS') {
147 shortcut = shortcut.replace('meta', 'cmd');
143 shortcut = shortcut.replace('meta', 'cmd');
@@ -182,7 +178,7 b' IPython.keyboard = (function (IPython) {'
182 this._shortcuts[shortcut] = data;
178 this._shortcuts[shortcut] = data;
183 if (!suppress_help_update) {
179 if (!suppress_help_update) {
184 // update the keyboard shortcuts notebook help
180 // update the keyboard shortcuts notebook help
185 $([IPython.events]).trigger('rebuild.QuickHelp');
181 this.events.trigger('rebuild.QuickHelp');
186 }
182 }
187 };
183 };
188
184
@@ -191,7 +187,7 b' IPython.keyboard = (function (IPython) {'
191 this.add_shortcut(shortcut, data[shortcut], true);
187 this.add_shortcut(shortcut, data[shortcut], true);
192 }
188 }
193 // update the keyboard shortcuts notebook help
189 // update the keyboard shortcuts notebook help
194 $([IPython.events]).trigger('rebuild.QuickHelp');
190 this.events.trigger('rebuild.QuickHelp');
195 };
191 };
196
192
197 ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
193 ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
@@ -200,7 +196,7 b' IPython.keyboard = (function (IPython) {'
200 delete this._shortcuts[shortcut];
196 delete this._shortcuts[shortcut];
201 if (!suppress_help_update) {
197 if (!suppress_help_update) {
202 // update the keyboard shortcuts notebook help
198 // update the keyboard shortcuts notebook help
203 $([IPython.events]).trigger('rebuild.QuickHelp');
199 this.events.trigger('rebuild.QuickHelp');
204 }
200 }
205 };
201 };
206
202
@@ -211,7 +207,7 b' IPython.keyboard = (function (IPython) {'
211 var timer = null;
207 var timer = null;
212 if (c[shortcut] === data.count-1) {
208 if (c[shortcut] === data.count-1) {
213 c[shortcut] = 0;
209 c[shortcut] = 0;
214 var timer = t[shortcut];
210 timer = t[shortcut];
215 if (timer) {clearTimeout(timer); delete t[shortcut];}
211 if (timer) {clearTimeout(timer); delete t[shortcut];}
216 return data.handler(event);
212 return data.handler(event);
217 } else {
213 } else {
@@ -228,7 +224,7 b' IPython.keyboard = (function (IPython) {'
228 var shortcut = event_to_shortcut(event);
224 var shortcut = event_to_shortcut(event);
229 var data = this._shortcuts[shortcut];
225 var data = this._shortcuts[shortcut];
230 if (data) {
226 if (data) {
231 var handler = data['handler'];
227 var handler = data.handler;
232 if (handler) {
228 if (handler) {
233 if (data.count === 1) {
229 if (data.count === 1) {
234 return handler(event);
230 return handler(event);
@@ -243,10 +239,10 b' IPython.keyboard = (function (IPython) {'
243 ShortcutManager.prototype.handles = function (event) {
239 ShortcutManager.prototype.handles = function (event) {
244 var shortcut = event_to_shortcut(event);
240 var shortcut = event_to_shortcut(event);
245 var data = this._shortcuts[shortcut];
241 var data = this._shortcuts[shortcut];
246 return !( data === undefined || data.handler === undefined )
242 return !( data === undefined || data.handler === undefined );
247 }
243 };
248
244
249 return {
245 var keyboard = {
250 keycodes : keycodes,
246 keycodes : keycodes,
251 inv_keycodes : inv_keycodes,
247 inv_keycodes : inv_keycodes,
252 ShortcutManager : ShortcutManager,
248 ShortcutManager : ShortcutManager,
@@ -256,4 +252,8 b' IPython.keyboard = (function (IPython) {'
256 event_to_shortcut : event_to_shortcut
252 event_to_shortcut : event_to_shortcut
257 };
253 };
258
254
259 }(IPython));
255 // For backwards compatability.
256 IPython.keyboard = keyboard;
257
258 return keyboard;
259 });
@@ -1,34 +1,8 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 var IPython = IPython || {};
4 var IPython = IPython || {};
9
5 define([], function(){
10 IPython.version = "3.0.0-dev";
6 IPython.version = "3.0.0-dev";
11
7 return IPython;
12 IPython.namespace = function (ns_string) {
8 });
13 "use strict";
14
15 var parts = ns_string.split('.'),
16 parent = IPython,
17 i;
18
19 // String redundant leading global
20 if (parts[0] === "IPython") {
21 parts = parts.slice(1);
22 }
23
24 for (i=0; i<parts.length; i+=1) {
25 // Create property if it doesn't exist
26 if (typeof parent[parts[i]] === "undefined") {
27 parent[parts[i]] = {};
28 }
29 }
30 return parent;
31 };
32
33
34
@@ -1,32 +1,19 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Global header/site setup.
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 ], function(IPython, $){
12 var IPython = (function (IPython) {
13 "use strict";
8 "use strict";
14
9
15 var Page = function () {
10 var Page = function () {
16 this.style();
17 this.bind_events();
11 this.bind_events();
18 };
12 };
19
13
20 Page.prototype.style = function () {
21 $('div#header').addClass('border-box-sizing');
22 $('div#site').addClass('border-box-sizing');
23 };
24
25
26 Page.prototype.bind_events = function () {
14 Page.prototype.bind_events = function () {
27 };
15 };
28
16
29
30 Page.prototype.show = function () {
17 Page.prototype.show = function () {
31 // The header and site divs start out hidden to prevent FLOUC.
18 // The header and site divs start out hidden to prevent FLOUC.
32 // Main scripts should call this method after styling everything.
19 // Main scripts should call this method after styling everything.
@@ -34,23 +21,21 b' var IPython = (function (IPython) {'
34 this.show_site();
21 this.show_site();
35 };
22 };
36
23
37
38 Page.prototype.show_header = function () {
24 Page.prototype.show_header = function () {
39 // The header and site divs start out hidden to prevent FLOUC.
25 // The header and site divs start out hidden to prevent FLOUC.
40 // Main scripts should call this method after styling everything.
26 // Main scripts should call this method after styling everything.
27 // TODO: selector are hardcoded, pass as constructor argument
41 $('div#header').css('display','block');
28 $('div#header').css('display','block');
42 };
29 };
43
30
44
45 Page.prototype.show_site = function () {
31 Page.prototype.show_site = function () {
46 // The header and site divs start out hidden to prevent FLOUC.
32 // The header and site divs start out hidden to prevent FLOUC.
47 // Main scripts should call this method after styling everything.
33 // Main scripts should call this method after styling everything.
34 // TODO: selector are hardcoded, pass as constructor argument
48 $('div#site').css('display','block');
35 $('div#site').css('display','block');
49 };
36 };
50
37
51
38 // Register self in the global namespace for convenience.
52 IPython.Page = Page;
39 IPython.Page = Page;
53
40 return {'Page': Page};
54 return IPython;
41 });
55
56 }(IPython));
@@ -1,19 +1,12 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2014 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Utilities
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11 IPython.namespace('IPython.security');
7 'components/google-caja/html-css-sanitizer-minified',
12
8 ], function(IPython, $) {
13 IPython.security = (function (IPython) {
14 "use strict";
9 "use strict";
15
16 var utils = IPython.utils;
17
10
18 var noop = function (x) { return x; };
11 var noop = function (x) { return x; };
19
12
@@ -117,10 +110,12 b' IPython.security = (function (IPython) {'
117 return sanitized;
110 return sanitized;
118 };
111 };
119
112
120 return {
113 var security = {
121 caja: caja,
114 caja: caja,
122 sanitize_html: sanitize_html
115 sanitize_html: sanitize_html
123 };
116 };
124
117
125 }(IPython));
118 IPython.security = security;
126
119
120 return security;
121 });
@@ -1,13 +1,10 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 //============================================================================
4 define([
5 // Utilities
5 'base/js/namespace',
6 //============================================================================
6 'jquery',
7
7 ], function(IPython, $){
8 IPython.namespace('IPython.utils');
9
10 IPython.utils = (function (IPython) {
11 "use strict";
8 "use strict";
12
9
13 IPython.load_extensions = function () {
10 IPython.load_extensions = function () {
@@ -517,19 +514,24 b' IPython.utils = (function (IPython) {'
517 }
514 }
518 };
515 };
519
516
517 var ajax_error_msg = function (jqXHR) {
518 // Return a JSON error message if there is one,
519 // otherwise the basic HTTP status text.
520 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
521 return jqXHR.responseJSON.message;
522 } else {
523 return jqXHR.statusText;
524 }
525 }
520 var log_ajax_error = function (jqXHR, status, error) {
526 var log_ajax_error = function (jqXHR, status, error) {
521 // log ajax failures with informative messages
527 // log ajax failures with informative messages
522 var msg = "API request failed (" + jqXHR.status + "): ";
528 var msg = "API request failed (" + jqXHR.status + "): ";
523 console.log(jqXHR);
529 console.log(jqXHR);
524 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
530 msg += ajax_error_msg(jqXHR);
525 msg += jqXHR.responseJSON.message;
526 } else {
527 msg += jqXHR.statusText;
528 }
529 console.log(msg);
531 console.log(msg);
530 };
532 };
531
533
532 return {
534 var utils = {
533 regex_split : regex_split,
535 regex_split : regex_split,
534 uuid : uuid,
536 uuid : uuid,
535 fixConsole : fixConsole,
537 fixConsole : fixConsole,
@@ -550,8 +552,12 b' IPython.utils = (function (IPython) {'
550 platform: platform,
552 platform: platform,
551 is_or_has : is_or_has,
553 is_or_has : is_or_has,
552 is_focused : is_focused,
554 is_focused : is_focused,
555 ajax_error_msg : ajax_error_msg,
553 log_ajax_error : log_ajax_error,
556 log_ajax_error : log_ajax_error,
554 };
557 };
555
558
556 }(IPython));
559 // Backwards compatability.
557
560 IPython.utils = utils;
561
562 return utils;
563 });
@@ -184,6 +184,30 b' Browsers not listed, including Safari, are supported via the styling under the'
184 justify-content: center;
184 justify-content: center;
185 }
185 }
186
186
187 .hbox.baseline,
188 .vbox.baseline,
189 .baseline {
190 /* Old browsers */
191 -webkit-box-pack: baseline;
192 -moz-box-pack: baseline;
193 box-pack: baseline;
194
195 /* Modern browsers */
196 justify-content: baseline;
197 }
198
199 .hbox.stretch,
200 .vbox.stretch,
201 .stretch {
202 /* Old browsers */
203 -webkit-box-pack: stretch;
204 -moz-box-pack: stretch;
205 box-pack: stretch;
206
207 /* Modern browsers */
208 justify-content: stretch;
209 }
210
187 .hbox.align-start,
211 .hbox.align-start,
188 .vbox.align-start,
212 .vbox.align-start,
189 .align-start {
213 .align-start {
@@ -219,3 +243,27 b' Browsers not listed, including Safari, are supported via the styling under the'
219 /* Modern browsers */
243 /* Modern browsers */
220 align-items: center;
244 align-items: center;
221 }
245 }
246
247 .hbox.align-baseline,
248 .vbox.align-baseline,
249 .align-baseline {
250 /* Old browsers */
251 -webkit-box-align: baseline;
252 -moz-box-align: baseline;
253 box-align: baseline;
254
255 /* Modern browsers */
256 align-items: baseline;
257 }
258
259 .hbox.align-stretch,
260 .vbox.align-stretch,
261 .align-stretch {
262 /* Old browsers */
263 -webkit-box-align: stretch;
264 -moz-box-align: stretch;
265 box-align: stretch;
266
267 /* Modern browsers */
268 align-items: stretch;
269 }
@@ -24,6 +24,7 b' div#header {'
24 padding-left: 30px;
24 padding-left: 30px;
25 padding-bottom: 5px;
25 padding-bottom: 5px;
26 border-bottom: 1px solid @navbar-default-border;
26 border-bottom: 1px solid @navbar-default-border;
27 .border-box-sizing();
27 }
28 }
28
29
29 #ipython_notebook {
30 #ipython_notebook {
@@ -50,6 +51,7 b' div#header {'
50 #site {
51 #site {
51 width: 100%;
52 width: 100%;
52 display: none;
53 display: none;
54 .border-box-sizing();
53 }
55 }
54
56
55 /* Smaller buttons */
57 /* Smaller buttons */
@@ -69,6 +71,14 b' span#login_widget {'
69 float: right;
71 float: right;
70 }
72 }
71
73
74 span#login_widget > .button,
75 #logout
76 {
77 .btn();
78 .btn-default();
79 .btn-sm();
80 }
81
72 .nav-header {
82 .nav-header {
73 text-transform: none;
83 text-transform: none;
74 }
84 }
@@ -93,3 +103,8 b' span#login_widget {'
93 }
103 }
94 }
104 }
95
105
106 // less mixin to be sure to add the right class to get icons with font awesome.
107 .icon(@ico){
108 .fa();
109 content: @ico;
110 }
@@ -1,3 +1,8 b''
1 /*!
2 *
3 * IPython base
4 *
5 */
1 @import "variables.less";
6 @import "variables.less";
2 @import "mixins.less";
7 @import "mixins.less";
3 @import "flexbox.less";
8 @import "flexbox.less";
@@ -1,1 +1,1 b''
1 Subproject commit 7c07f705d4f068828fb76f74ac7d4d355aea06f9
1 Subproject commit b3909af1b61ca7a412481759fdb441ecdfb3ab66
@@ -12,12 +12,12 b''
12 *
12 *
13 * Same thing with `profile/static/custom/custom.css` to inject custom css into the notebook.
13 * Same thing with `profile/static/custom/custom.css` to inject custom css into the notebook.
14 *
14 *
15 * Example :
15 * __Example 1:__
16 *
16 *
17 * Create a custom button in toolbar that execute `%qtconsole` in kernel
17 * Create a custom button in toolbar that execute `%qtconsole` in kernel
18 * and hence open a qtconsole attached to the same kernel as the current notebook
18 * and hence open a qtconsole attached to the same kernel as the current notebook
19 *
19 *
20 * $([IPython.events]).on('app_initialized.NotebookApp', function(){
20 * IPython.events.on('app_initialized.NotebookApp', function(){
21 * IPython.toolbar.add_buttons_group([
21 * IPython.toolbar.add_buttons_group([
22 * {
22 * {
23 * 'label' : 'run qtconsole',
23 * 'label' : 'run qtconsole',
@@ -30,7 +30,16 b''
30 * ]);
30 * ]);
31 * });
31 * });
32 *
32 *
33 * Example :
33 * __Example 2:__
34 *
35 * At the completion of the dashboard loading, load an unofficial javascript extension
36 * that is installed in profile/static/custom/
37 *
38 * IPython.events.on('app_initialized.DashboardApp', function(){
39 * require(['custom/unofficial_extension.js'])
40 * });
41 *
42 * __Example 3:__
34 *
43 *
35 * Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );`
44 * Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );`
36 * to load custom script into the notebook.
45 * to load custom script into the notebook.
@@ -122,4 +122,4 b' dateFormat.i18n = {'
122 // For convenience...
122 // For convenience...
123 Date.prototype.format = function (mask, utc) {
123 Date.prototype.format = function (mask, utc) {
124 return dateFormat(this, mask, utc);
124 return dateFormat(this, mask, utc);
125 };
125 }; No newline at end of file
@@ -1,53 +1,61 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 'base/js/utils',
8 //============================================================================
8 ], function(IPython, $, utils) {
9 // Cell
9 // TODO: remove IPython dependency here
10 //============================================================================
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
14 * @namespace IPython
15 * @submodule Cell
16 */
17
18 var IPython = (function (IPython) {
19 "use strict";
10 "use strict";
20
11
21 var utils = IPython.utils;
12 // monkey patch CM to be able to syntax highlight cell magics
22 var keycodes = IPython.keyboard.keycodes;
13 // bug reported upstream,
14 // see https://github.com/marijnh/CodeMirror2/issues/670
15 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
16 CodeMirror.modes.null = function() {
17 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
18 };
19 }
23
20
24 /**
21 CodeMirror.patchedGetMode = function(config, mode){
25 * The Base `Cell` class from which to inherit
22 var cmmode = CodeMirror.getMode(config, mode);
26 * @class Cell
23 if(cmmode.indent === null) {
27 **/
24 console.log('patch mode "' , mode, '" on the fly');
25 cmmode.indent = function(){return 0;};
26 }
27 return cmmode;
28 };
29 // end monkey patching CodeMirror
28
30
29 /*
30 * @constructor
31 *
32 * @param {object|undefined} [options]
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
34 */
35 var Cell = function (options) {
31 var Cell = function (options) {
36
32 // Constructor
37 options = this.mergeopt(Cell, options);
33 //
34 // The Base `Cell` class from which to inherit.
35 //
36 // Parameters:
37 // options: dictionary
38 // Dictionary of keyword arguments.
39 // events: $(Events) instance
40 // config: dictionary
41 // keyboard_manager: KeyboardManager instance
42 options = options || {};
43 this.keyboard_manager = options.keyboard_manager;
44 this.events = options.events;
45 var config = this.mergeopt(Cell, options.config);
38 // superclass default overwrite our default
46 // superclass default overwrite our default
39
47
40 this.placeholder = options.placeholder || '';
48 this.placeholder = config.placeholder || '';
41 this.read_only = options.cm_config.readOnly;
49 this.read_only = config.cm_config.readOnly;
42 this.selected = false;
50 this.selected = false;
43 this.rendered = false;
51 this.rendered = false;
44 this.mode = 'command';
52 this.mode = 'command';
45 this.metadata = {};
53 this.metadata = {};
46 // load this from metadata later ?
54 // load this from metadata later ?
47 this.user_highlight = 'auto';
55 this.user_highlight = 'auto';
48 this.cm_config = options.cm_config;
56 this.cm_config = config.cm_config;
49 this.cell_id = utils.uuid();
57 this.cell_id = utils.uuid();
50 this._options = options;
58 this._options = config;
51
59
52 // For JS VM engines optimization, attributes should be all set (even
60 // For JS VM engines optimization, attributes should be all set (even
53 // to null) in the constructor, and if possible, if different subclass
61 // to null) in the constructor, and if possible, if different subclass
@@ -70,7 +78,12 b' var IPython = (function (IPython) {'
70 cm_config : {
78 cm_config : {
71 indentUnit : 4,
79 indentUnit : 4,
72 readOnly: false,
80 readOnly: false,
73 theme: "default"
81 theme: "default",
82 extraKeys: {
83 "Cmd-Right":"goLineRight",
84 "End":"goLineRight",
85 "Cmd-Left":"goLineLeft"
86 }
74 }
87 }
75 };
88 };
76
89
@@ -127,27 +140,27 b' var IPython = (function (IPython) {'
127 // We trigger events so that Cell doesn't have to depend on Notebook.
140 // We trigger events so that Cell doesn't have to depend on Notebook.
128 that.element.click(function (event) {
141 that.element.click(function (event) {
129 if (!that.selected) {
142 if (!that.selected) {
130 $([IPython.events]).trigger('select.Cell', {'cell':that});
143 that.events.trigger('select.Cell', {'cell':that});
131 }
144 }
132 });
145 });
133 that.element.focusin(function (event) {
146 that.element.focusin(function (event) {
134 if (!that.selected) {
147 if (!that.selected) {
135 $([IPython.events]).trigger('select.Cell', {'cell':that});
148 that.events.trigger('select.Cell', {'cell':that});
136 }
149 }
137 });
150 });
138 if (this.code_mirror) {
151 if (this.code_mirror) {
139 this.code_mirror.on("change", function(cm, change) {
152 this.code_mirror.on("change", function(cm, change) {
140 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
153 that.events.trigger("set_dirty.Notebook", {value: true});
141 });
154 });
142 }
155 }
143 if (this.code_mirror) {
156 if (this.code_mirror) {
144 this.code_mirror.on('focus', function(cm, change) {
157 this.code_mirror.on('focus', function(cm, change) {
145 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
158 that.events.trigger('edit_mode.Cell', {cell: that});
146 });
159 });
147 }
160 }
148 if (this.code_mirror) {
161 if (this.code_mirror) {
149 this.code_mirror.on('blur', function(cm, change) {
162 this.code_mirror.on('blur', function(cm, change) {
150 $([IPython.events]).trigger('command_mode.Cell', {cell: that});
163 that.events.trigger('command_mode.Cell', {cell: that});
151 });
164 });
152 }
165 }
153 };
166 };
@@ -165,8 +178,7 b' var IPython = (function (IPython) {'
165 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
178 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
166 */
179 */
167 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
180 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
168 var that = this;
181 var shortcuts = this.keyboard_manager.edit_shortcuts;
169 var shortcuts = IPython.keyboard_manager.edit_shortcuts;
170
182
171 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
183 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
172 // manager will handle it
184 // manager will handle it
@@ -544,9 +556,8 b' var IPython = (function (IPython) {'
544 this.code_mirror.setOption('mode', default_mode);
556 this.code_mirror.setOption('mode', default_mode);
545 };
557 };
546
558
559 // Backwards compatibility.
547 IPython.Cell = Cell;
560 IPython.Cell = Cell;
548
561
549 return IPython;
562 return {'Cell': Cell};
550
563 });
551 }(IPython));
552
@@ -1,32 +1,28 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 'base/js/events'
8 //============================================================================
8 ], function(IPython, $, events) {
9 // CellToolbar
10 //============================================================================
11
12
13 /**
14 * A Module to control the per-cell toolbar.
15 * @module IPython
16 * @namespace IPython
17 * @submodule CellToolbar
18 */
19 var IPython = (function (IPython) {
20 "use strict";
9 "use strict";
21
10
22 /**
11 var CellToolbar = function (options) {
23 * @constructor
12 // Constructor
24 * @class CellToolbar
13 //
25 * @param {The cell to attach the metadata UI to} cell
14 // Parameters:
26 */
15 // options: dictionary
27 var CellToolbar = function (cell) {
16 // Dictionary of keyword arguments.
17 // events: $(Events) instance
18 // cell: Cell instance
19 // notebook: Notebook instance
20 //
21 // TODO: This leaks, when cell are deleted
22 // There is still a reference to each celltoolbars.
28 CellToolbar._instances.push(this);
23 CellToolbar._instances.push(this);
29 this.cell = cell;
24 this.notebook = options.notebook;
25 this.cell = options.cell;
30 this.create_element();
26 this.create_element();
31 this.rebuild();
27 this.rebuild();
32 return this;
28 return this;
@@ -34,7 +30,7 b' var IPython = (function (IPython) {'
34
30
35
31
36 CellToolbar.prototype.create_element = function () {
32 CellToolbar.prototype.create_element = function () {
37 this.inner_element = $('<div/>').addClass('celltoolbar')
33 this.inner_element = $('<div/>').addClass('celltoolbar');
38 this.element = $('<div/>').addClass('ctb_hideshow')
34 this.element = $('<div/>').addClass('ctb_hideshow')
39 .append(this.inner_element);
35 .append(this.inner_element);
40 };
36 };
@@ -182,13 +178,14 b' var IPython = (function (IPython) {'
182 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
178 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
183 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
179 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
184 */
180 */
185 CellToolbar.register_preset = function(name, preset_list) {
181 CellToolbar.register_preset = function(name, preset_list, notebook) {
186 CellToolbar._presets[name] = preset_list;
182 CellToolbar._presets[name] = preset_list;
187 $([IPython.events]).trigger('preset_added.CellToolbar', {name: name});
183 events.trigger('preset_added.CellToolbar', {name: name});
188 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
184 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
189 // In that case, activate the preset if needed.
185 // In that case, activate the preset if needed.
190 if (IPython.notebook && IPython.notebook.metadata && IPython.notebook.metadata.celltoolbar === name)
186 if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){
191 this.activate_preset(name);
187 CellToolbar.activate_preset(name);
188 }
192 };
189 };
193
190
194
191
@@ -229,7 +226,7 b' var IPython = (function (IPython) {'
229 CellToolbar.rebuild_all();
226 CellToolbar.rebuild_all();
230 }
227 }
231
228
232 $([IPython.events]).trigger('preset_activated.CellToolbar', {name: preset_name});
229 events.trigger('preset_activated.CellToolbar', {name: preset_name});
233 };
230 };
234
231
235
232
@@ -283,7 +280,7 b' var IPython = (function (IPython) {'
283 }
280 }
284
281
285 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
282 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
286 if (!this.ui_controls_list.length || (this.cell instanceof IPython.TextCell && this.cell.rendered)) {
283 if (!this.ui_controls_list.length || (this.cell.cell_type != 'code' && this.cell.rendered)) {
287 this.hide();
284 this.hide();
288 } else {
285 } else {
289 this.show();
286 this.show();
@@ -347,7 +344,7 b' var IPython = (function (IPython) {'
347 setter(cell, !v);
344 setter(cell, !v);
348 chkb.attr("checked", !v);
345 chkb.attr("checked", !v);
349 });
346 });
350 button_container.append($('<div/>').append(lbl));
347 button_container.append($('<span/>').append(lbl));
351 };
348 };
352 };
349 };
353
350
@@ -411,12 +408,12 b' var IPython = (function (IPython) {'
411 select.change(function(){
408 select.change(function(){
412 setter(cell, select.val());
409 setter(cell, select.val());
413 });
410 });
414 button_container.append($('<div/>').append(lbl).append(select));
411 button_container.append($('<span/>').append(lbl).append(select));
415 };
412 };
416 };
413 };
417
414
418
415 // Backwards compatability.
419 IPython.CellToolbar = CellToolbar;
416 IPython.CellToolbar = CellToolbar;
420
417
421 return IPython;
418 return {'CellToolbar': CellToolbar};
422 }(IPython));
419 });
@@ -1,31 +1,29 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'jquery',
6 //----------------------------------------------------------------------------
6 'notebook/js/celltoolbar',
7
7 'base/js/dialog',
8 //============================================================================
8 ], function($, celltoolbar, dialog) {
9 // CellToolbar Default
10 //============================================================================
11
12 /**
13 * Example Use for the CellToolbar library
14 */
15 // IIFE without asignement, we don't modifiy the IPython namespace
16 (function (IPython) {
17 "use strict";
9 "use strict";
18
10
19 var CellToolbar = IPython.CellToolbar;
11 var CellToolbar = celltoolbar.CellToolbar;
20
12
21 var raw_edit = function(cell){
13 var raw_edit = function (cell) {
22 IPython.dialog.edit_metadata(cell.metadata, function (md) {
14 dialog.edit_metadata({
23 cell.metadata = md;
15 md: cell.metadata,
16 callback: function (md) {
17 cell.metadata = md;
18 },
19 name: 'Cell',
20 notebook: this.notebook,
21 keyboard_manager: this.keyboard_manager
24 });
22 });
25 };
23 };
26
24
27 var add_raw_edit_button = function(div, cell) {
25 var add_raw_edit_button = function(div, cell) {
28 var button_container = div;
26 var button_container = $(div);
29 var button = $('<button/>')
27 var button = $('<button/>')
30 .addClass("btn btn-default btn-xs")
28 .addClass("btn btn-default btn-xs")
31 .text("Edit Metadata")
29 .text("Edit Metadata")
@@ -36,11 +34,18 b''
36 button_container.append(button);
34 button_container.append(button);
37 };
35 };
38
36
39 CellToolbar.register_callback('default.rawedit', add_raw_edit_button);
37 var register = function (notebook) {
40 var example_preset = [];
38 CellToolbar.register_callback('default.rawedit', add_raw_edit_button);
41 example_preset.push('default.rawedit');
39 raw_edit = $.proxy(raw_edit, {
40 notebook: notebook,
41 keyboard_manager: notebook.keyboard_manager
42 });
42
43
43 CellToolbar.register_preset('Edit Metadata', example_preset);
44 var example_preset = [];
44 console.log('Default extension for cell metadata editing loaded.');
45 example_preset.push('default.rawedit');
45
46
46 }(IPython));
47 CellToolbar.register_preset('Edit Metadata', example_preset, notebook);
48 console.log('Default extension for cell metadata editing loaded.');
49 };
50 return {'register': register};
51 });
@@ -1,28 +1,20 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 // Example Use for the CellToolbar library
5 // the file COPYING, distributed as part of this software.
5 // add the following to your custom.js to load
6 //----------------------------------------------------------------------------
6 // Celltoolbar UI for slideshow
7
7
8 //============================================================================
8 // ```
9 // CellToolbar Example
9 // $.getScript('/static/js/celltoolbarpresets/example.js');
10 //============================================================================
10 // ```
11
11 define([
12 /**
12 'jquery',
13 * Example Use for the CellToolbar library
13 'notebook/js/celltoolbar',
14 * add the following to your custom.js to load
14 ], function($, celltoolbar) {
15 * Celltoolbar UI for slideshow
16 *
17 * ```
18 * $.getScript('/static/js/celltoolbarpresets/example.js');
19 * ```
20 */
21 // IIFE without asignement, we don't modifiy the IPython namespace
22 (function (IPython) {
23 "use strict";
15 "use strict";
24
16
25 var CellToolbar = IPython.CellToolbar;
17 var CellToolbar = celltoolbar.CellToolbar;
26
18
27 var example_preset = [];
19 var example_preset = [];
28
20
@@ -32,32 +24,32 b''
32 var fun = function(value){
24 var fun = function(value){
33 try{
25 try{
34 if(value){
26 if(value){
35 cell.code_mirror.setOption('readOnly','nocursor')
27 cell.code_mirror.setOption('readOnly','nocursor');
36 button.button('option','icons',{primary:'ui-icon-locked'})
28 button.button('option','icons',{primary:'ui-icon-locked'});
37 } else {
29 } else {
38 cell.code_mirror.setOption('readOnly',false)
30 cell.code_mirror.setOption('readOnly',false);
39 button.button('option','icons',{primary:'ui-icon-unlocked'})
31 button.button('option','icons',{primary:'ui-icon-unlocked'});
40 }
32 }
41 } catch(e){}
33 } catch(e){}
42
34
43 }
35 };
44 fun(cell.metadata.ro)
36 fun(cell.metadata.ro);
45 button.click(function(){
37 button.click(function(){
46 var v = cell.metadata.ro;
38 var v = cell.metadata.ro;
47 var locked = !v;
39 var locked = !v;
48 cell.metadata.ro = locked;
40 cell.metadata.ro = locked;
49 fun(locked)
41 fun(locked);
50 })
42 })
51 .css('height','16px')
43 .css('height','16px')
52 .css('width','35px');
44 .css('width','35px');
53 button_container.append(button);
45 button_container.append(button);
54 }
46 };
55
47
56 CellToolbar.register_callback('example.lock',simple_button);
48 CellToolbar.register_callback('example.lock',simple_button);
57 example_preset.push('example.lock');
49 example_preset.push('example.lock');
58
50
59 var toggle_test = function(div, cell) {
51 var toggle_test = function(div, cell) {
60 var button_container = $(div)
52 var button_container = $(div);
61 var button = $('<div/>')
53 var button = $('<div/>')
62 .button({label:String(cell.metadata.foo)}).
54 .button({label:String(cell.metadata.foo)}).
63 css('width','65px');
55 css('width','65px');
@@ -65,9 +57,9 b''
65 var v = cell.metadata.foo;
57 var v = cell.metadata.foo;
66 cell.metadata.foo = !v;
58 cell.metadata.foo = !v;
67 button.button("option","label",String(!v));
59 button.button("option","label",String(!v));
68 })
60 });
69 button_container.append(button);
61 button_container.append(button);
70 }
62 };
71
63
72 CellToolbar.register_callback('example.toggle',toggle_test);
64 CellToolbar.register_callback('example.toggle',toggle_test);
73 example_preset.push('example.toggle');
65 example_preset.push('example.toggle');
@@ -76,16 +68,16 b''
76 // setter
68 // setter
77 function(cell, value){
69 function(cell, value){
78 // we check that the slideshow namespace exist and create it if needed
70 // we check that the slideshow namespace exist and create it if needed
79 if (cell.metadata.yn_test == undefined){cell.metadata.yn_test = {}}
71 if (cell.metadata.yn_test === undefined){cell.metadata.yn_test = {};}
80 // set the value
72 // set the value
81 cell.metadata.yn_test.value = value
73 cell.metadata.yn_test.value = value;
82 },
74 },
83 //geter
75 //geter
84 function(cell){ var ns = cell.metadata.yn_test;
76 function(cell){ var ns = cell.metadata.yn_test;
85 // if the slideshow namespace does not exist return `undefined`
77 // if the slideshow namespace does not exist return `undefined`
86 // (will be interpreted as `false` by checkbox) otherwise
78 // (will be interpreted as `false` by checkbox) otherwise
87 // return the value
79 // return the value
88 return (ns == undefined)? undefined: ns.value
80 return (ns === undefined)? undefined: ns.value;
89 }
81 }
90 );
82 );
91
83
@@ -103,16 +95,16 b''
103 // setter
95 // setter
104 function(cell,value){
96 function(cell,value){
105 // we check that the slideshow namespace exist and create it if needed
97 // we check that the slideshow namespace exist and create it if needed
106 if (cell.metadata.test == undefined){cell.metadata.test = {}}
98 if (cell.metadata.test === undefined){cell.metadata.test = {};}
107 // set the value
99 // set the value
108 cell.metadata.test.slide_type = value
100 cell.metadata.test.slide_type = value;
109 },
101 },
110 //geter
102 //geter
111 function(cell){ var ns = cell.metadata.test;
103 function(cell){ var ns = cell.metadata.test;
112 // if the slideshow namespace does not exist return `undefined`
104 // if the slideshow namespace does not exist return `undefined`
113 // (will be interpreted as `false` by checkbox) otherwise
105 // (will be interpreted as `false` by checkbox) otherwise
114 // return the value
106 // return the value
115 return (ns == undefined)? undefined: ns.slide_type
107 return (ns === undefined)? undefined: ns.slide_type;
116 });
108 });
117
109
118 CellToolbar.register_callback('example.select',select_test);
110 CellToolbar.register_callback('example.select',select_test);
@@ -120,7 +112,7 b''
120
112
121 var simple_dialog = function(title,text){
113 var simple_dialog = function(title,text){
122 var dlg = $('<div/>').attr('title',title)
114 var dlg = $('<div/>').attr('title',title)
123 .append($('<p/>').text(text))
115 .append($('<p/>').text(text));
124 $(dlg).dialog({
116 $(dlg).dialog({
125 autoOpen: true,
117 autoOpen: true,
126 height: 300,
118 height: 300,
@@ -131,24 +123,26 b''
131 $(this).remove();
123 $(this).remove();
132 }
124 }
133 });
125 });
134 }
126 };
135
127
136 var add_simple_dialog_button = function(div, cell) {
128 var add_simple_dialog_button = function(div, cell) {
137 var help_text = ["This is the Metadata editting UI.",
129 var help_text = ["This is the Metadata editting UI.",
138 "It heavily rely on plugin to work ",
130 "It heavily rely on plugin to work ",
139 "and is still under developpement. You shouldn't wait too long before",
131 "and is still under developpement. You shouldn't wait too long before",
140 " seeing some customisable buttons in those toolbar."
132 " seeing some customisable buttons in those toolbar."
141 ].join('\n')
133 ].join('\n');
142 var button_container = $(div)
134 var button_container = $(div);
143 var button = $('<div/>').button({label:'?'})
135 var button = $('<div/>').button({label:'?'})
144 .click(function(){simple_dialog('help',help_text); return false;})
136 .click(function(){simple_dialog('help',help_text); return false;});
145 button_container.append(button);
137 button_container.append(button);
146 }
138 };
147
148 CellToolbar.register_callback('example.help',add_simple_dialog_button)
149 example_preset.push('example.help')
150
139
151 CellToolbar.register_preset('Example',example_preset);
140 var register = function (notebook) {
152 console.log('Example extension for metadata editing loaded.');
141 CellToolbar.register_callback('example.help',add_simple_dialog_button);
142 example_preset.push('example.help');
153
143
154 }(IPython));
144 CellToolbar.register_preset('Example',example_preset, notebook);
145 console.log('Example extension for metadata editing loaded.');
146 };
147 return {'register': register};
148 });
@@ -1,18 +1,15 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // CellToolbar Example
5 'jquery',
10 //============================================================================
6 'notebook/js/celltoolbar',
7 'base/js/dialog',
8 'base/js/keyboard',
9 ], function($, celltoolbar, dialog, keyboard) {
10 "use strict";
11
11
12 (function(IPython) {
12 var CellToolbar = celltoolbar.CellToolbar;
13 "use strict";
14
15 var CellToolbar = IPython.CellToolbar;
16 var raw_cell_preset = [];
13 var raw_cell_preset = [];
17
14
18 var select_type = CellToolbar.utils.select_ui_generator([
15 var select_type = CellToolbar.utils.select_ui_generator([
@@ -39,7 +36,7 b''
39 $('<input/>').attr('type','text').attr('size','25')
36 $('<input/>').attr('type','text').attr('size','25')
40 .val(cell.metadata.raw_mimetype || "-")
37 .val(cell.metadata.raw_mimetype || "-")
41 );
38 );
42 IPython.dialog.modal({
39 dialog.modal({
43 title: "Raw Cell MIME Type",
40 title: "Raw Cell MIME Type",
44 body: dialog,
41 body: dialog,
45 buttons : {
42 buttons : {
@@ -57,7 +54,7 b''
57 var that = $(this);
54 var that = $(this);
58 // Upon ENTER, click the OK button.
55 // Upon ENTER, click the OK button.
59 that.find('input[type="text"]').keydown(function (event, ui) {
56 that.find('input[type="text"]').keydown(function (event, ui) {
60 if (event.which === IPython.keyboard.keycodes.enter) {
57 if (event.which === keyboard.keycodes.enter) {
61 that.find('.btn-primary').first().click();
58 that.find('.btn-primary').first().click();
62 return false;
59 return false;
63 }
60 }
@@ -77,11 +74,13 b''
77 "Raw NBConvert Format"
74 "Raw NBConvert Format"
78 );
75 );
79
76
80 CellToolbar.register_callback('raw_cell.select', select_type, ['raw']);
77 var register = function (notebook) {
81
78 CellToolbar.register_callback('raw_cell.select', select_type, ['raw']);
82 raw_cell_preset.push('raw_cell.select');
79 raw_cell_preset.push('raw_cell.select');
83
80
84 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
81 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset, notebook);
85 console.log('Raw Cell Format toolbar preset loaded.');
82 console.log('Raw Cell Format toolbar preset loaded.');
83 };
84 return {'register': register};
86
85
87 }(IPython));
86 });
@@ -1,19 +1,14 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 //CellToolbar Example
5 'jquery',
10 //============================================================================
6 'notebook/js/celltoolbar',
11
7 ], function($, celltoolbar) {
12 // IIFE without asignement, we don't modifiy the IPython namespace
13 (function (IPython) {
14 "use strict";
8 "use strict";
15
9
16 var CellToolbar = IPython.CellToolbar;
10
11 var CellToolbar = celltoolbar.CellToolbar;
17 var slideshow_preset = [];
12 var slideshow_preset = [];
18
13
19 var select_type = CellToolbar.utils.select_ui_generator([
14 var select_type = CellToolbar.utils.select_ui_generator([
@@ -27,24 +22,25 b''
27 // setter
22 // setter
28 function(cell, value){
23 function(cell, value){
29 // we check that the slideshow namespace exist and create it if needed
24 // we check that the slideshow namespace exist and create it if needed
30 if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
25 if (cell.metadata.slideshow === undefined){cell.metadata.slideshow = {};}
31 // set the value
26 // set the value
32 cell.metadata.slideshow.slide_type = value
27 cell.metadata.slideshow.slide_type = value;
33 },
28 },
34 //geter
29 //geter
35 function(cell){ var ns = cell.metadata.slideshow;
30 function(cell){ var ns = cell.metadata.slideshow;
36 // if the slideshow namespace does not exist return `undefined`
31 // if the slideshow namespace does not exist return `undefined`
37 // (will be interpreted as `false` by checkbox) otherwise
32 // (will be interpreted as `false` by checkbox) otherwise
38 // return the value
33 // return the value
39 return (ns == undefined)? undefined: ns.slide_type
34 return (ns === undefined)? undefined: ns.slide_type;
40 },
35 },
41 "Slide Type");
36 "Slide Type");
42
37
43 CellToolbar.register_callback('slideshow.select',select_type);
38 var register = function (notebook) {
44
39 CellToolbar.register_callback('slideshow.select',select_type);
45 slideshow_preset.push('slideshow.select');
40 slideshow_preset.push('slideshow.select');
46
47 CellToolbar.register_preset('Slideshow',slideshow_preset);
48 console.log('Slideshow extension for metadata editing loaded.');
49
41
50 }(IPython));
42 CellToolbar.register_preset('Slideshow',slideshow_preset, notebook);
43 console.log('Slideshow extension for metadata editing loaded.');
44 };
45 return {'register': register};
46 });
@@ -1,68 +1,67 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 'base/js/utils',
8 //============================================================================
8 'base/js/keyboard',
9 // CodeCell
9 'notebook/js/cell',
10 //============================================================================
10 'notebook/js/outputarea',
11 /**
11 'notebook/js/completer',
12 * An extendable module that provide base functionnality to create cell for notebook.
12 'notebook/js/celltoolbar',
13 * @module IPython
13 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) {
14 * @namespace IPython
15 * @submodule CodeCell
16 */
17
18
19 /* local util for codemirror */
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21
22 /**
23 *
24 * function to delete until previous non blanking space character
25 * or first multiple of 4 tabstop.
26 * @private
27 */
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 var tabsize = cm.getOption('tabSize');
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 var select = cm.getRange(from,cur);
36 if( select.match(/^\ +$/) !== null){
37 cm.replaceRange("",from,cur);
38 } else {
39 cm.deleteH(-1,"char");
40 }
41 };
42
43
44 var IPython = (function (IPython) {
45 "use strict";
14 "use strict";
15 var Cell = cell.Cell;
46
16
47 var utils = IPython.utils;
17 /* local util for codemirror */
48 var keycodes = IPython.keyboard.keycodes;
18 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
49
19
50 /**
20 /**
51 * A Cell conceived to write code.
52 *
21 *
53 * The kernel doesn't have to be set at creation time, in that case
22 * function to delete until previous non blanking space character
54 * it will be null and set_kernel has to be called later.
23 * or first multiple of 4 tabstop.
55 * @class CodeCell
24 * @private
56 * @extends IPython.Cell
57 *
58 * @constructor
59 * @param {Object|null} kernel
60 * @param {object|undefined} [options]
61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 */
25 */
26 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
27 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
28 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
29 var cur = cm.getCursor(), line = cm.getLine(cur.line);
30 var tabsize = cm.getOption('tabSize');
31 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
32 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
33 var select = cm.getRange(from,cur);
34 if( select.match(/^\ +$/) !== null){
35 cm.replaceRange("",from,cur);
36 } else {
37 cm.deleteH(-1,"char");
38 }
39 };
40
41 var keycodes = keyboard.keycodes;
42
63 var CodeCell = function (kernel, options) {
43 var CodeCell = function (kernel, options) {
44 // Constructor
45 //
46 // A Cell conceived to write code.
47 //
48 // Parameters:
49 // kernel: Kernel instance
50 // The kernel doesn't have to be set at creation time, in that case
51 // it will be null and set_kernel has to be called later.
52 // options: dictionary
53 // Dictionary of keyword arguments.
54 // events: $(Events) instance
55 // config: dictionary
56 // keyboard_manager: KeyboardManager instance
57 // notebook: Notebook instance
58 // tooltip: Tooltip instance
64 this.kernel = kernel || null;
59 this.kernel = kernel || null;
60 this.notebook = options.notebook;
65 this.collapsed = false;
61 this.collapsed = false;
62 this.events = options.events;
63 this.tooltip = options.tooltip;
64 this.config = options.config;
66
65
67 // create all attributed in constructor function
66 // create all attributed in constructor function
68 // even if null for V8 VM optimisation
67 // even if null for V8 VM optimisation
@@ -77,9 +76,11 b' var IPython = (function (IPython) {'
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 };
77 };
79
78
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
81
80 Cell.apply(this,[{
82 IPython.Cell.apply(this,[options]);
81 config: config,
82 keyboard_manager: options.keyboard_manager,
83 events: this.events}]);
83
84
84 // Attributes we want to override in this subclass.
85 // Attributes we want to override in this subclass.
85 this.cell_type = "code";
86 this.cell_type = "code";
@@ -109,29 +110,31 b' var IPython = (function (IPython) {'
109
110
110 CodeCell.msg_cells = {};
111 CodeCell.msg_cells = {};
111
112
112 CodeCell.prototype = new IPython.Cell();
113 CodeCell.prototype = new Cell();
113
114
114 /**
115 /**
115 * @method auto_highlight
116 * @method auto_highlight
116 */
117 */
117 CodeCell.prototype.auto_highlight = function () {
118 CodeCell.prototype.auto_highlight = function () {
118 this._auto_highlight(IPython.config.cell_magic_highlight);
119 this._auto_highlight(this.config.cell_magic_highlight);
119 };
120 };
120
121
121 /** @method create_element */
122 /** @method create_element */
122 CodeCell.prototype.create_element = function () {
123 CodeCell.prototype.create_element = function () {
123 IPython.Cell.prototype.create_element.apply(this, arguments);
124 Cell.prototype.create_element.apply(this, arguments);
124
125
125 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
126 var cell = $('<div></div>').addClass('cell code_cell');
126 cell.attr('tabindex','2');
127 cell.attr('tabindex','2');
127
128
128 var input = $('<div></div>').addClass('input');
129 var input = $('<div></div>').addClass('input');
129 var prompt = $('<div/>').addClass('prompt input_prompt');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
130 var inner_cell = $('<div/>').addClass('inner_cell');
131 var inner_cell = $('<div/>').addClass('inner_cell');
131 this.celltoolbar = new IPython.CellToolbar(this);
132 this.celltoolbar = new celltoolbar.CellToolbar({
133 cell: this,
134 notebook: this.notebook});
132 inner_cell.append(this.celltoolbar.element);
135 inner_cell.append(this.celltoolbar.element);
133 var input_area = $('<div/>').addClass('input_area');
136 var input_area = $('<div/>').addClass('input_area');
134 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
137 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
135 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
138 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
136 inner_cell.append(input_area);
139 inner_cell.append(input_area);
137 input.append(prompt).append(inner_cell);
140 input.append(prompt).append(inner_cell);
@@ -158,13 +161,17 b' var IPython = (function (IPython) {'
158 var output = $('<div></div>');
161 var output = $('<div></div>');
159 cell.append(input).append(widget_area).append(output);
162 cell.append(input).append(widget_area).append(output);
160 this.element = cell;
163 this.element = cell;
161 this.output_area = new IPython.OutputArea(output, true);
164 this.output_area = new outputarea.OutputArea({
162 this.completer = new IPython.Completer(this);
165 selector: output,
166 prompt_area: true,
167 events: this.events,
168 keyboard_manager: this.keyboard_manager});
169 this.completer = new completer.Completer(this, this.events);
163 };
170 };
164
171
165 /** @method bind_events */
172 /** @method bind_events */
166 CodeCell.prototype.bind_events = function () {
173 CodeCell.prototype.bind_events = function () {
167 IPython.Cell.prototype.bind_events.apply(this);
174 Cell.prototype.bind_events.apply(this);
168 var that = this;
175 var that = this;
169
176
170 this.element.focusout(
177 this.element.focusout(
@@ -187,7 +194,7 b' var IPython = (function (IPython) {'
187 // they are sent, and remove tooltip if any, except for tab again
194 // they are sent, and remove tooltip if any, except for tab again
188 var tooltip_closed = null;
195 var tooltip_closed = null;
189 if (event.type === 'keydown' && event.which != keycodes.tab ) {
196 if (event.type === 'keydown' && event.which != keycodes.tab ) {
190 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
197 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
191 }
198 }
192
199
193 var cur = editor.getCursor();
200 var cur = editor.getCursor();
@@ -195,21 +202,21 b' var IPython = (function (IPython) {'
195 this.auto_highlight();
202 this.auto_highlight();
196 }
203 }
197
204
198 if (event.which === keycodes.down && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
205 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
199 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
206 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
200 // browser and keyboard layout !
207 // browser and keyboard layout !
201 // Pressing '(' , request tooltip, don't forget to reappend it
208 // Pressing '(' , request tooltip, don't forget to reappend it
202 // The second argument says to hide the tooltip if the docstring
209 // The second argument says to hide the tooltip if the docstring
203 // is actually empty
210 // is actually empty
204 IPython.tooltip.pending(that, true);
211 this.tooltip.pending(that, true);
205 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
212 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
206 // If tooltip is active, cancel it. The call to
213 // If tooltip is active, cancel it. The call to
207 // remove_and_cancel_tooltip above doesn't pass, force=true.
214 // remove_and_cancel_tooltip above doesn't pass, force=true.
208 // Because of this it won't actually close the tooltip
215 // Because of this it won't actually close the tooltip
209 // if it is in sticky mode. Thus, we have to check again if it is open
216 // if it is in sticky mode. Thus, we have to check again if it is open
210 // and close it with force=true.
217 // and close it with force=true.
211 if (!IPython.tooltip._hidden) {
218 if (!this.tooltip._hidden) {
212 IPython.tooltip.remove_and_cancel_tooltip(true);
219 this.tooltip.remove_and_cancel_tooltip(true);
213 }
220 }
214 // If we closed the tooltip, don't let CM or the global handlers
221 // If we closed the tooltip, don't let CM or the global handlers
215 // handle this event.
222 // handle this event.
@@ -223,12 +230,12 b' var IPython = (function (IPython) {'
223 return false;
230 return false;
224 }
231 }
225 }
232 }
226 IPython.tooltip.request(that);
233 this.tooltip.request(that);
227 event.stop();
234 event.stop();
228 return true;
235 return true;
229 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
236 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
230 // Tab completion.
237 // Tab completion.
231 IPython.tooltip.remove_and_cancel_tooltip();
238 this.tooltip.remove_and_cancel_tooltip();
232 if (editor.somethingSelected()) {
239 if (editor.somethingSelected()) {
233 return false;
240 return false;
234 }
241 }
@@ -246,7 +253,7 b' var IPython = (function (IPython) {'
246
253
247 // keyboard event wasn't one of those unique to code cells, let's see
254 // keyboard event wasn't one of those unique to code cells, let's see
248 // if it's one of the generic ones (i.e. check edit mode shortcuts)
255 // if it's one of the generic ones (i.e. check edit mode shortcuts)
249 return IPython.Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
256 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
250 };
257 };
251
258
252 // Kernel related calls.
259 // Kernel related calls.
@@ -305,7 +312,7 b' var IPython = (function (IPython) {'
305 };
312 };
306
313
307 CodeCell.prototype._open_with_pager = function (payload) {
314 CodeCell.prototype._open_with_pager = function (payload) {
308 $([IPython.events]).trigger('open_with_text.Pager', payload);
315 this.events.trigger('open_with_text.Pager', payload);
309 };
316 };
310
317
311 /**
318 /**
@@ -315,7 +322,7 b' var IPython = (function (IPython) {'
315 CodeCell.prototype._handle_execute_reply = function (msg) {
322 CodeCell.prototype._handle_execute_reply = function (msg) {
316 this.set_input_prompt(msg.content.execution_count);
323 this.set_input_prompt(msg.content.execution_count);
317 this.element.removeClass("running");
324 this.element.removeClass("running");
318 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
325 this.events.trigger('set_dirty.Notebook', {value: true});
319 };
326 };
320
327
321 /**
328 /**
@@ -324,7 +331,7 b' var IPython = (function (IPython) {'
324 */
331 */
325 CodeCell.prototype._handle_set_next_input = function (payload) {
332 CodeCell.prototype._handle_set_next_input = function (payload) {
326 var data = {'cell': this, 'text': payload.text};
333 var data = {'cell': this, 'text': payload.text};
327 $([IPython.events]).trigger('set_next_input.Notebook', data);
334 this.events.trigger('set_next_input.Notebook', data);
328 };
335 };
329
336
330 /**
337 /**
@@ -339,7 +346,7 b' var IPython = (function (IPython) {'
339 // Basic cell manipulation.
346 // Basic cell manipulation.
340
347
341 CodeCell.prototype.select = function () {
348 CodeCell.prototype.select = function () {
342 var cont = IPython.Cell.prototype.select.apply(this);
349 var cont = Cell.prototype.select.apply(this);
343 if (cont) {
350 if (cont) {
344 this.code_mirror.refresh();
351 this.code_mirror.refresh();
345 this.auto_highlight();
352 this.auto_highlight();
@@ -348,7 +355,7 b' var IPython = (function (IPython) {'
348 };
355 };
349
356
350 CodeCell.prototype.render = function () {
357 CodeCell.prototype.render = function () {
351 var cont = IPython.Cell.prototype.render.apply(this);
358 var cont = Cell.prototype.render.apply(this);
352 // Always execute, even if we are already in the rendered state
359 // Always execute, even if we are already in the rendered state
353 return cont;
360 return cont;
354 };
361 };
@@ -451,7 +458,7 b' var IPython = (function (IPython) {'
451 // JSON serialization
458 // JSON serialization
452
459
453 CodeCell.prototype.fromJSON = function (data) {
460 CodeCell.prototype.fromJSON = function (data) {
454 IPython.Cell.prototype.fromJSON.apply(this, arguments);
461 Cell.prototype.fromJSON.apply(this, arguments);
455 if (data.cell_type === 'code') {
462 if (data.cell_type === 'code') {
456 if (data.input !== undefined) {
463 if (data.input !== undefined) {
457 this.set_text(data.input);
464 this.set_text(data.input);
@@ -479,7 +486,7 b' var IPython = (function (IPython) {'
479
486
480
487
481 CodeCell.prototype.toJSON = function () {
488 CodeCell.prototype.toJSON = function () {
482 var data = IPython.Cell.prototype.toJSON.apply(this);
489 var data = Cell.prototype.toJSON.apply(this);
483 data.input = this.get_text();
490 data.input = this.get_text();
484 // is finite protect against undefined and '*' value
491 // is finite protect against undefined and '*' value
485 if (isFinite(this.input_prompt_number)) {
492 if (isFinite(this.input_prompt_number)) {
@@ -499,11 +506,11 b' var IPython = (function (IPython) {'
499 * @return is the action being taken
506 * @return is the action being taken
500 */
507 */
501 CodeCell.prototype.unselect = function () {
508 CodeCell.prototype.unselect = function () {
502 var cont = IPython.Cell.prototype.unselect.apply(this);
509 var cont = Cell.prototype.unselect.apply(this);
503 if (cont) {
510 if (cont) {
504 // When a code cell is usnelected, make sure that the corresponding
511 // When a code cell is usnelected, make sure that the corresponding
505 // tooltip and completer to that cell is closed.
512 // tooltip and completer to that cell is closed.
506 IPython.tooltip.remove_and_cancel_tooltip(true);
513 this.tooltip.remove_and_cancel_tooltip(true);
507 if (this.completer !== null) {
514 if (this.completer !== null) {
508 this.completer.close();
515 this.completer.close();
509 }
516 }
@@ -511,7 +518,8 b' var IPython = (function (IPython) {'
511 return cont;
518 return cont;
512 };
519 };
513
520
521 // Backwards compatability.
514 IPython.CodeCell = CodeCell;
522 IPython.CodeCell = CodeCell;
515
523
516 return IPython;
524 return {'CodeCell': CodeCell};
517 }(IPython));
525 });
@@ -7,10 +7,15 b" CodeMirror.requireMode('python',function(){"
7 "use strict";
7 "use strict";
8
8
9 CodeMirror.defineMode("ipython", function(conf, parserConf) {
9 CodeMirror.defineMode("ipython", function(conf, parserConf) {
10
10 var pythonConf = {};
11 parserConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
11 for (var prop in parserConf) {
12 parserConf.name = 'python'
12 if (parserConf.hasOwnProperty(prop)) {
13 return CodeMirror.getMode(conf, parserConf);
13 pythonConf[prop] = parserConf[prop];
14 }
15 }
16 pythonConf.name = 'python';
17 pythonConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
18 return CodeMirror.getMode(conf, pythonConf);
14 }, 'python');
19 }, 'python');
15
20
16 CodeMirror.defineMIME("text/x-ipython", "ipython");
21 CodeMirror.defineMIME("text/x-ipython", "ipython");
@@ -8,7 +8,6 b''
8
8
9 CodeMirror.requireMode('gfm', function(){
9 CodeMirror.requireMode('gfm', function(){
10 CodeMirror.requireMode('stex', function(){
10 CodeMirror.requireMode('stex', function(){
11 console.log('defining custom mode...');
12 CodeMirror.defineMode("ipythongfm", function(config, parserConfig) {
11 CodeMirror.defineMode("ipythongfm", function(config, parserConfig) {
13
12
14 var gfm_mode = CodeMirror.getMode(config, "gfm");
13 var gfm_mode = CodeMirror.getMode(config, "gfm");
@@ -1,17 +1,17 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 // Completer
4 define([
5 //
5 'base/js/namespace',
6 // Completer is be a class that takes a cell instance.
6 'jquery',
7
7 'base/js/utils',
8 var IPython = (function (IPython) {
8 'base/js/keyboard',
9 // that will prevent us from misspelling
9 'notebook/js/contexthint',
10 ], function(IPython, $, utils, keyboard) {
10 "use strict";
11 "use strict";
11
12
12 // easier key mapping
13 // easier key mapping
13 var keycodes = IPython.keyboard.keycodes;
14 var keycodes = keyboard.keycodes;
14 var utils = IPython.utils;
15
15
16 var prepend_n_prc = function(str, n) {
16 var prepend_n_prc = function(str, n) {
17 for( var i =0 ; i< n ; i++){
17 for( var i =0 ; i< n ; i++){
@@ -78,14 +78,14 b' var IPython = (function (IPython) {'
78 }
78 }
79
79
80
80
81 var Completer = function (cell) {
81 var Completer = function (cell, events) {
82 this.cell = cell;
82 this.cell = cell;
83 this.editor = cell.code_mirror;
83 this.editor = cell.code_mirror;
84 var that = this;
84 var that = this;
85 $([IPython.events]).on('status_busy.Kernel', function () {
85 events.on('status_busy.Kernel', function () {
86 that.skip_kernel_completion = true;
86 that.skip_kernel_completion = true;
87 });
87 });
88 $([IPython.events]).on('status_idle.Kernel', function () {
88 events.on('status_idle.Kernel', function () {
89 that.skip_kernel_completion = false;
89 that.skip_kernel_completion = false;
90 });
90 });
91 };
91 };
@@ -351,6 +351,18 b' var IPython = (function (IPython) {'
351 }
351 }
352 index = Math.min(Math.max(index, 0), options.length-1);
352 index = Math.min(Math.max(index, 0), options.length-1);
353 this.sel[0].selectedIndex = index;
353 this.sel[0].selectedIndex = index;
354 } else if (code == keycodes.pageup || code == keycodes.pagedown) {
355 CodeMirror.e_stop(event);
356
357 var options = this.sel.find('option');
358 var index = this.sel[0].selectedIndex;
359 if (code == keycodes.pageup) {
360 index -= 10; // As 10 is the hard coded size of the drop down menu
361 } else {
362 index += 10;
363 }
364 index = Math.min(Math.max(index, 0), options.length-1);
365 this.sel[0].selectedIndex = index;
354 } else if (code == keycodes.left || code == keycodes.right) {
366 } else if (code == keycodes.left || code == keycodes.right) {
355 this.close();
367 this.close();
356 }
368 }
@@ -379,7 +391,9 b' var IPython = (function (IPython) {'
379 that.carry_on_completion();
391 that.carry_on_completion();
380 }, 50);
392 }, 50);
381 };
393 };
394
395 // For backwards compatability.
382 IPython.Completer = Completer;
396 IPython.Completer = Completer;
383
397
384 return IPython;
398 return {'Completer': Completer};
385 }(IPython));
399 });
@@ -1,28 +1,9 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([], function() {
9 // Notebook
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 var IPython = (function (IPython) {
18 "use strict";
5 "use strict";
19 /**
6
20 * A place where some stuff can be confugured.
21 *
22 * @class config
23 * @static
24 *
25 **/
26 var default_config = {
7 var default_config = {
27 /**
8 /**
28 * Dictionary of object to autodetect highlight mode for code cell.
9 * Dictionary of object to autodetect highlight mode for code cell.
@@ -50,30 +31,25 b' var IPython = (function (IPython) {'
50 * cell_magic_highlight['javascript'] = {'reg':[/^var/]}
31 * cell_magic_highlight['javascript'] = {'reg':[/^var/]}
51 */
32 */
52 cell_magic_highlight : {
33 cell_magic_highlight : {
53 'magic_javascript' :{'reg':[/^%%javascript/]}
34 'magic_javascript' :{'reg':[/^%%javascript/]},
54 ,'magic_perl' :{'reg':[/^%%perl/]}
35 'magic_perl' :{'reg':[/^%%perl/]},
55 ,'magic_ruby' :{'reg':[/^%%ruby/]}
36 'magic_ruby' :{'reg':[/^%%ruby/]},
56 ,'magic_python' :{'reg':[/^%%python3?/]}
37 'magic_python' :{'reg':[/^%%python3?/]},
57 ,'magic_shell' :{'reg':[/^%%bash/]}
38 'magic_shell' :{'reg':[/^%%bash/]},
58 ,'magic_r' :{'reg':[/^%%R/]}
39 'magic_r' :{'reg':[/^%%R/]},
59 ,'magic_text/x-cython' :{'reg':[/^%%cython/]}
40 'magic_text/x-cython' :{'reg':[/^%%cython/]},
60 },
41 },
61
42
62 /**
43 /**
63 * same as `cell_magic_highlight` but for raw cells
44 * same as `cell_magic_highlight` but for raw cells
64 * @attribute raw_cell_highlight
45 * @attribute raw_cell_highlight
65 */
46 */
66 raw_cell_highlight : {
47 raw_cell_highlight : {
67 'diff' :{'reg':[/^diff/]}
48 'diff' :{'reg':[/^diff/]}
68 },
49 },
69
50 };
70 };
51
71
52 return {
72 // use the same method to merge user configuration
53 'default_config': default_config,
73 IPython.config = {};
54 };
74 $.extend(IPython.config, default_config);
55 });
75
76 return IPython;
77
78 }(IPython));
79
@@ -1,12 +1,15 b''
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
1 // highly adapted for codemiror jshint
4 // highly adapted for codemiror jshint
2 (function () {
5 define([], function() {
3 "use strict";
6 "use strict";
4
7
5 function forEach(arr, f) {
8 var forEach = function(arr, f) {
6 for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
9 for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
7 }
10 };
8
11
9 function arrayContains(arr, item) {
12 var arrayContains = function(arr, item) {
10 if (!Array.prototype.indexOf) {
13 if (!Array.prototype.indexOf) {
11 var i = arr.length;
14 var i = arr.length;
12 while (i--) {
15 while (i--) {
@@ -17,7 +20,7 b''
17 return false;
20 return false;
18 }
21 }
19 return arr.indexOf(item) != -1;
22 return arr.indexOf(item) != -1;
20 }
23 };
21
24
22 CodeMirror.contextHint = function (editor) {
25 CodeMirror.contextHint = function (editor) {
23 // Find the token at the cursor
26 // Find the token at the cursor
@@ -26,7 +29,7 b''
26 tprop = token;
29 tprop = token;
27 // If it's not a 'word-style' token, ignore the token.
30 // If it's not a 'word-style' token, ignore the token.
28 // If it is a property, find out what it is a property of.
31 // If it is a property, find out what it is a property of.
29 var list = new Array();
32 var list = [];
30 var clist = getCompletions(token, editor);
33 var clist = getCompletions(token, editor);
31 for (var i = 0; i < clist.length; i++) {
34 for (var i = 0; i < clist.length; i++) {
32 list.push({
35 list.push({
@@ -40,55 +43,56 b''
40 line: cur.line,
43 line: cur.line,
41 ch: token.end
44 ch: token.end
42 }
45 }
43 })
46 });
44 }
47 }
45 return list;
48 return list;
46 }
49 };
47
50
48 // find all 'words' of current cell
51 // find all 'words' of current cell
49 var getAllTokens = function (editor) {
52 var getAllTokens = function (editor) {
50 var found = [];
53 var found = [];
51
54
52 // add to found if not already in it
55 // add to found if not already in it
53
56
54
57
55 function maybeAdd(str) {
58 function maybeAdd(str) {
56 if (!arrayContains(found, str)) found.push(str);
59 if (!arrayContains(found, str)) found.push(str);
57 }
60 }
58
61
59 // loop through all token on all lines
62 // loop through all token on all lines
60 var lineCount = editor.lineCount();
63 var lineCount = editor.lineCount();
61 // loop on line
64 // loop on line
62 for (var l = 0; l < lineCount; l++) {
65 for (var l = 0; l < lineCount; l++) {
63 var line = editor.getLine(l);
66 var line = editor.getLine(l);
64 //loop on char
67 //loop on char
65 for (var c = 1; c < line.length; c++) {
68 for (var c = 1; c < line.length; c++) {
66 var tk = editor.getTokenAt({
69 var tk = editor.getTokenAt({
67 line: l,
70 line: l,
68 ch: c
71 ch: c
69 });
72 });
70 // if token has a class, it has geat chances of beeing
73 // if token has a class, it has geat chances of beeing
71 // of interest. Add it to the list of possible completions.
74 // of interest. Add it to the list of possible completions.
72 // we could skip token of ClassName 'comment'
75 // we could skip token of ClassName 'comment'
73 // or 'number' and 'operator'
76 // or 'number' and 'operator'
74 if (tk.className != null) {
77 if (tk.className !== null) {
75 maybeAdd(tk.string);
78 maybeAdd(tk.string);
76 }
77 // jump to char after end of current token
78 c = tk.end;
79 }
79 }
80 // jump to char after end of current token
81 c = tk.end;
80 }
82 }
81 return found;
82 }
83 }
84 return found;
85 };
83
86
84
87 var getCompletions = function(token, editor) {
85 function getCompletions(token, editor) {
86 var candidates = getAllTokens(editor);
88 var candidates = getAllTokens(editor);
87 // filter all token that have a common start (but nox exactly) the lenght of the current token
89 // filter all token that have a common start (but nox exactly) the lenght of the current token
88 var lambda = function (x) {
90 var lambda = function (x) {
89 return (x.indexOf(token.string) == 0 && x != token.string)
91 return (x.indexOf(token.string) === 0 && x != token.string);
90 };
92 };
91 var filterd = candidates.filter(lambda);
93 var filterd = candidates.filter(lambda);
92 return filterd;
94 return filterd;
93 }
95 };
94 })();
96
97 return {'contextHint': CodeMirror.contextHint};
98 });
This diff has been collapsed as it changes many lines, (902 lines changed) Show them Hide them
@@ -1,468 +1,476 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Keyboard management
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 'base/js/utils',
12 var IPython = (function (IPython) {
8 'base/js/keyboard',
9 ], function(IPython, $, utils, keyboard) {
13 "use strict";
10 "use strict";
11
12 var browser = utils.browser[0];
13 var platform = utils.platform;
14
14
15 var browser = IPython.utils.browser[0];
15 // Main keyboard manager for the notebook
16 var platform = IPython.utils.platform;
16 var keycodes = keyboard.keycodes;
17
17
18 // Default keyboard shortcuts
18 var KeyboardManager = function (options) {
19 // Constructor
20 //
21 // Parameters:
22 // options: dictionary
23 // Dictionary of keyword arguments.
24 // events: $(Events) instance
25 // pager: Pager instance
26 this.mode = 'command';
27 this.enabled = true;
28 this.pager = options.pager;
29 this.quick_help = undefined;
30 this.notebook = undefined;
31 this.bind_events();
32 this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
33 this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
34 this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
35 this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
36 this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
37 this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
38 };
19
39
20 var default_common_shortcuts = {
40 KeyboardManager.prototype.get_default_common_shortcuts = function() {
21 'shift' : {
41 var that = this;
22 help : '',
42 var shortcuts = {
23 help_index : '',
43 'shift' : {
24 handler : function (event) {
44 help : '',
25 // ignore shift keydown
45 help_index : '',
26 return true;
46 handler : function (event) {
27 }
47 // ignore shift keydown
28 },
48 return true;
29 'shift-enter' : {
49 }
30 help : 'run cell, select below',
50 },
31 help_index : 'ba',
51 'shift-enter' : {
32 handler : function (event) {
52 help : 'run cell, select below',
33 IPython.notebook.execute_cell_and_select_below();
53 help_index : 'ba',
34 return false;
54 handler : function (event) {
35 }
55 that.notebook.execute_cell_and_select_below();
36 },
56 return false;
37 'ctrl-enter' : {
57 }
38 help : 'run cell',
58 },
39 help_index : 'bb',
59 'ctrl-enter' : {
40 handler : function (event) {
60 help : 'run cell',
41 IPython.notebook.execute_cell();
61 help_index : 'bb',
42 return false;
62 handler : function (event) {
43 }
63 that.notebook.execute_cell();
44 },
64 return false;
45 'alt-enter' : {
65 }
46 help : 'run cell, insert below',
66 },
47 help_index : 'bc',
67 'alt-enter' : {
48 handler : function (event) {
68 help : 'run cell, insert below',
49 IPython.notebook.execute_cell_and_insert_below();
69 help_index : 'bc',
50 return false;
70 handler : function (event) {
71 that.notebook.execute_cell_and_insert_below();
72 return false;
73 }
51 }
74 }
75 };
76
77 if (platform === 'MacOS') {
78 shortcuts['cmd-s'] =
79 {
80 help : 'save notebook',
81 help_index : 'fb',
82 handler : function (event) {
83 that.notebook.save_checkpoint();
84 event.preventDefault();
85 return false;
86 }
87 };
88 } else {
89 shortcuts['ctrl-s'] =
90 {
91 help : 'save notebook',
92 help_index : 'fb',
93 handler : function (event) {
94 that.notebook.save_checkpoint();
95 event.preventDefault();
96 return false;
97 }
98 };
52 }
99 }
100 return shortcuts;
53 };
101 };
54
102
55 if (platform === 'MacOS') {
103 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
56 default_common_shortcuts['cmd-s'] =
104 var that = this;
57 {
105 return {
58 help : 'save notebook',
106 'esc' : {
59 help_index : 'fb',
107 help : 'command mode',
108 help_index : 'aa',
60 handler : function (event) {
109 handler : function (event) {
61 IPython.notebook.save_checkpoint();
110 that.notebook.command_mode();
62 event.preventDefault();
63 return false;
111 return false;
64 }
112 }
65 };
113 },
66 } else {
114 'ctrl-m' : {
67 default_common_shortcuts['ctrl-s'] =
115 help : 'command mode',
68 {
116 help_index : 'ab',
69 help : 'save notebook',
70 help_index : 'fb',
71 handler : function (event) {
117 handler : function (event) {
72 IPython.notebook.save_checkpoint();
118 that.notebook.command_mode();
73 event.preventDefault();
74 return false;
119 return false;
75 }
120 }
76 };
121 },
77 }
122 'up' : {
78
123 help : '',
79 // Edit mode defaults
124 help_index : '',
80
125 handler : function (event) {
81 var default_edit_shortcuts = {
126 var index = that.notebook.get_selected_index();
82 'esc' : {
127 var cell = that.notebook.get_cell(index);
83 help : 'command mode',
128 if (cell && cell.at_top() && index !== 0) {
84 help_index : 'aa',
129 event.preventDefault();
85 handler : function (event) {
130 that.notebook.command_mode();
86 IPython.notebook.command_mode();
131 that.notebook.select_prev();
87 return false;
132 that.notebook.edit_mode();
88 }
133 var cm = that.notebook.get_selected_cell().code_mirror;
89 },
134 cm.setCursor(cm.lastLine(), 0);
90 'ctrl-m' : {
135 return false;
91 help : 'command mode',
136 } else if (cell) {
92 help_index : 'ab',
137 var cm = cell.code_mirror;
93 handler : function (event) {
138 cm.execCommand('goLineUp');
94 IPython.notebook.command_mode();
139 return false;
95 return false;
140 }
96 }
141 }
97 },
142 },
98 'up' : {
143 'down' : {
99 help : '',
144 help : '',
100 help_index : '',
145 help_index : '',
101 handler : function (event) {
146 handler : function (event) {
102 var index = IPython.notebook.get_selected_index();
147 var index = that.notebook.get_selected_index();
103 var cell = IPython.notebook.get_cell(index);
148 var cell = that.notebook.get_cell(index);
104 if (cell && cell.at_top() && index !== 0) {
149 if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
105 event.preventDefault();
150 event.preventDefault();
106 IPython.notebook.command_mode();
151 that.notebook.command_mode();
107 IPython.notebook.select_prev();
152 that.notebook.select_next();
108 IPython.notebook.edit_mode();
153 that.notebook.edit_mode();
109 var cm = IPython.notebook.get_selected_cell().code_mirror;
154 var cm = that.notebook.get_selected_cell().code_mirror;
110 cm.setCursor(cm.lastLine(), 0);
155 cm.setCursor(0, 0);
111 return false;
156 return false;
112 } else if (cell) {
157 } else {
113 var cm = cell.code_mirror;
158 var cm = cell.code_mirror;
114 cm.execCommand('goLineUp');
159 cm.execCommand('goLineDown');
160 return false;
161 }
162 }
163 },
164 'ctrl-shift--' : {
165 help : 'split cell',
166 help_index : 'ea',
167 handler : function (event) {
168 that.notebook.split_cell();
115 return false;
169 return false;
116 }
170 }
117 }
171 },
118 },
172 'ctrl-shift-subtract' : {
119 'down' : {
173 help : '',
120 help : '',
174 help_index : 'eb',
121 help_index : '',
175 handler : function (event) {
122 handler : function (event) {
176 that.notebook.split_cell();
123 var index = IPython.notebook.get_selected_index();
124 var cell = IPython.notebook.get_cell(index);
125 if (cell.at_bottom() && index !== (IPython.notebook.ncells()-1)) {
126 event.preventDefault();
127 IPython.notebook.command_mode();
128 IPython.notebook.select_next();
129 IPython.notebook.edit_mode();
130 var cm = IPython.notebook.get_selected_cell().code_mirror;
131 cm.setCursor(0, 0);
132 return false;
133 } else {
134 var cm = cell.code_mirror;
135 cm.execCommand('goLineDown');
136 return false;
177 return false;
137 }
178 }
138 }
179 },
139 },
180 };
140 'ctrl-shift--' : {
141 help : 'split cell',
142 help_index : 'ea',
143 handler : function (event) {
144 IPython.notebook.split_cell();
145 return false;
146 }
147 },
148 'ctrl-shift-subtract' : {
149 help : '',
150 help_index : 'eb',
151 handler : function (event) {
152 IPython.notebook.split_cell();
153 return false;
154 }
155 },
156 };
181 };
157
182
158 // Command mode defaults
183 KeyboardManager.prototype.get_default_command_shortcuts = function() {
159
184 var that = this;
160 var default_command_shortcuts = {
185 return {
161 'enter' : {
186 'enter' : {
162 help : 'edit mode',
187 help : 'edit mode',
163 help_index : 'aa',
188 help_index : 'aa',
164 handler : function (event) {
189 handler : function (event) {
165 IPython.notebook.edit_mode();
190 that.notebook.edit_mode();
166 return false;
191 return false;
167 }
168 },
169 'up' : {
170 help : 'select previous cell',
171 help_index : 'da',
172 handler : function (event) {
173 var index = IPython.notebook.get_selected_index();
174 if (index !== 0 && index !== null) {
175 IPython.notebook.select_prev();
176 IPython.notebook.focus_cell();
177 }
192 }
178 return false;
193 },
179 }
194 'up' : {
180 },
195 help : 'select previous cell',
181 'down' : {
196 help_index : 'da',
182 help : 'select next cell',
197 handler : function (event) {
183 help_index : 'db',
198 var index = that.notebook.get_selected_index();
184 handler : function (event) {
199 if (index !== 0 && index !== null) {
185 var index = IPython.notebook.get_selected_index();
200 that.notebook.select_prev();
186 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
201 that.notebook.focus_cell();
187 IPython.notebook.select_next();
202 }
188 IPython.notebook.focus_cell();
203 return false;
189 }
204 }
190 return false;
205 },
191 }
206 'down' : {
192 },
207 help : 'select next cell',
193 'k' : {
208 help_index : 'db',
194 help : 'select previous cell',
209 handler : function (event) {
195 help_index : 'dc',
210 var index = that.notebook.get_selected_index();
196 handler : function (event) {
211 if (index !== (that.notebook.ncells()-1) && index !== null) {
197 var index = IPython.notebook.get_selected_index();
212 that.notebook.select_next();
198 if (index !== 0 && index !== null) {
213 that.notebook.focus_cell();
199 IPython.notebook.select_prev();
214 }
200 IPython.notebook.focus_cell();
215 return false;
201 }
216 }
202 return false;
217 },
203 }
218 'k' : {
204 },
219 help : 'select previous cell',
205 'j' : {
220 help_index : 'dc',
206 help : 'select next cell',
221 handler : function (event) {
207 help_index : 'dd',
222 var index = that.notebook.get_selected_index();
208 handler : function (event) {
223 if (index !== 0 && index !== null) {
209 var index = IPython.notebook.get_selected_index();
224 that.notebook.select_prev();
210 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
225 that.notebook.focus_cell();
211 IPython.notebook.select_next();
226 }
212 IPython.notebook.focus_cell();
227 return false;
213 }
228 }
214 return false;
229 },
215 }
230 'j' : {
216 },
231 help : 'select next cell',
217 'x' : {
232 help_index : 'dd',
218 help : 'cut cell',
233 handler : function (event) {
219 help_index : 'ee',
234 var index = that.notebook.get_selected_index();
220 handler : function (event) {
235 if (index !== (that.notebook.ncells()-1) && index !== null) {
221 IPython.notebook.cut_cell();
236 that.notebook.select_next();
222 return false;
237 that.notebook.focus_cell();
223 }
238 }
224 },
239 return false;
225 'c' : {
240 }
226 help : 'copy cell',
241 },
227 help_index : 'ef',
242 'x' : {
228 handler : function (event) {
243 help : 'cut cell',
229 IPython.notebook.copy_cell();
244 help_index : 'ee',
230 return false;
245 handler : function (event) {
231 }
246 that.notebook.cut_cell();
232 },
247 return false;
233 'shift-v' : {
248 }
234 help : 'paste cell above',
249 },
235 help_index : 'eg',
250 'c' : {
236 handler : function (event) {
251 help : 'copy cell',
237 IPython.notebook.paste_cell_above();
252 help_index : 'ef',
238 return false;
253 handler : function (event) {
239 }
254 that.notebook.copy_cell();
240 },
255 return false;
241 'v' : {
256 }
242 help : 'paste cell below',
257 },
243 help_index : 'eh',
258 'shift-v' : {
244 handler : function (event) {
259 help : 'paste cell above',
245 IPython.notebook.paste_cell_below();
260 help_index : 'eg',
246 return false;
261 handler : function (event) {
247 }
262 that.notebook.paste_cell_above();
248 },
263 return false;
249 'd' : {
264 }
250 help : 'delete cell (press twice)',
265 },
251 help_index : 'ej',
266 'v' : {
252 count: 2,
267 help : 'paste cell below',
253 handler : function (event) {
268 help_index : 'eh',
254 IPython.notebook.delete_cell();
269 handler : function (event) {
255 return false;
270 that.notebook.paste_cell_below();
256 }
271 return false;
257 },
272 }
258 'a' : {
273 },
259 help : 'insert cell above',
274 'd' : {
260 help_index : 'ec',
275 help : 'delete cell (press twice)',
261 handler : function (event) {
276 help_index : 'ej',
262 IPython.notebook.insert_cell_above();
277 count: 2,
263 IPython.notebook.select_prev();
278 handler : function (event) {
264 IPython.notebook.focus_cell();
279 that.notebook.delete_cell();
265 return false;
280 return false;
266 }
281 }
267 },
282 },
268 'b' : {
283 'a' : {
269 help : 'insert cell below',
284 help : 'insert cell above',
270 help_index : 'ed',
285 help_index : 'ec',
271 handler : function (event) {
286 handler : function (event) {
272 IPython.notebook.insert_cell_below();
287 that.notebook.insert_cell_above();
273 IPython.notebook.select_next();
288 that.notebook.select_prev();
274 IPython.notebook.focus_cell();
289 that.notebook.focus_cell();
275 return false;
290 return false;
276 }
291 }
277 },
292 },
278 'y' : {
293 'b' : {
279 help : 'to code',
294 help : 'insert cell below',
280 help_index : 'ca',
295 help_index : 'ed',
281 handler : function (event) {
296 handler : function (event) {
282 IPython.notebook.to_code();
297 that.notebook.insert_cell_below();
283 return false;
298 that.notebook.select_next();
284 }
299 that.notebook.focus_cell();
285 },
300 return false;
286 'm' : {
301 }
287 help : 'to markdown',
302 },
288 help_index : 'cb',
303 'y' : {
289 handler : function (event) {
304 help : 'to code',
290 IPython.notebook.to_markdown();
305 help_index : 'ca',
291 return false;
306 handler : function (event) {
292 }
307 that.notebook.to_code();
293 },
308 return false;
294 'r' : {
309 }
295 help : 'to raw',
310 },
296 help_index : 'cc',
311 'm' : {
297 handler : function (event) {
312 help : 'to markdown',
298 IPython.notebook.to_raw();
313 help_index : 'cb',
299 return false;
314 handler : function (event) {
300 }
315 that.notebook.to_markdown();
301 },
316 return false;
302 '1' : {
317 }
303 help : 'to heading 1',
318 },
304 help_index : 'cd',
319 'r' : {
305 handler : function (event) {
320 help : 'to raw',
306 IPython.notebook.to_heading(undefined, 1);
321 help_index : 'cc',
307 return false;
322 handler : function (event) {
308 }
323 that.notebook.to_raw();
309 },
324 return false;
310 '2' : {
325 }
311 help : 'to heading 2',
326 },
312 help_index : 'ce',
327 '1' : {
313 handler : function (event) {
328 help : 'to heading 1',
314 IPython.notebook.to_heading(undefined, 2);
329 help_index : 'cd',
315 return false;
330 handler : function (event) {
316 }
331 that.notebook.to_heading(undefined, 1);
317 },
332 return false;
318 '3' : {
333 }
319 help : 'to heading 3',
334 },
320 help_index : 'cf',
335 '2' : {
321 handler : function (event) {
336 help : 'to heading 2',
322 IPython.notebook.to_heading(undefined, 3);
337 help_index : 'ce',
323 return false;
338 handler : function (event) {
324 }
339 that.notebook.to_heading(undefined, 2);
325 },
340 return false;
326 '4' : {
341 }
327 help : 'to heading 4',
342 },
328 help_index : 'cg',
343 '3' : {
329 handler : function (event) {
344 help : 'to heading 3',
330 IPython.notebook.to_heading(undefined, 4);
345 help_index : 'cf',
331 return false;
346 handler : function (event) {
332 }
347 that.notebook.to_heading(undefined, 3);
333 },
348 return false;
334 '5' : {
349 }
335 help : 'to heading 5',
350 },
336 help_index : 'ch',
351 '4' : {
337 handler : function (event) {
352 help : 'to heading 4',
338 IPython.notebook.to_heading(undefined, 5);
353 help_index : 'cg',
339 return false;
354 handler : function (event) {
340 }
355 that.notebook.to_heading(undefined, 4);
341 },
356 return false;
342 '6' : {
357 }
343 help : 'to heading 6',
358 },
344 help_index : 'ci',
359 '5' : {
345 handler : function (event) {
360 help : 'to heading 5',
346 IPython.notebook.to_heading(undefined, 6);
361 help_index : 'ch',
347 return false;
362 handler : function (event) {
348 }
363 that.notebook.to_heading(undefined, 5);
349 },
364 return false;
350 'o' : {
365 }
351 help : 'toggle output',
366 },
352 help_index : 'gb',
367 '6' : {
353 handler : function (event) {
368 help : 'to heading 6',
354 IPython.notebook.toggle_output();
369 help_index : 'ci',
355 return false;
370 handler : function (event) {
356 }
371 that.notebook.to_heading(undefined, 6);
357 },
372 return false;
358 'shift-o' : {
373 }
359 help : 'toggle output scrolling',
374 },
360 help_index : 'gc',
375 'o' : {
361 handler : function (event) {
376 help : 'toggle output',
362 IPython.notebook.toggle_output_scroll();
377 help_index : 'gb',
363 return false;
378 handler : function (event) {
364 }
379 that.notebook.toggle_output();
365 },
380 return false;
366 's' : {
381 }
367 help : 'save notebook',
382 },
368 help_index : 'fa',
383 'shift-o' : {
369 handler : function (event) {
384 help : 'toggle output scrolling',
370 IPython.notebook.save_checkpoint();
385 help_index : 'gc',
371 return false;
386 handler : function (event) {
372 }
387 that.notebook.toggle_output_scroll();
373 },
388 return false;
374 'ctrl-j' : {
389 }
375 help : 'move cell down',
390 },
376 help_index : 'eb',
391 's' : {
377 handler : function (event) {
392 help : 'save notebook',
378 IPython.notebook.move_cell_down();
393 help_index : 'fa',
379 return false;
394 handler : function (event) {
380 }
395 that.notebook.save_checkpoint();
381 },
396 return false;
382 'ctrl-k' : {
397 }
383 help : 'move cell up',
398 },
384 help_index : 'ea',
399 'ctrl-j' : {
385 handler : function (event) {
400 help : 'move cell down',
386 IPython.notebook.move_cell_up();
401 help_index : 'eb',
387 return false;
402 handler : function (event) {
388 }
403 that.notebook.move_cell_down();
389 },
404 return false;
390 'l' : {
405 }
391 help : 'toggle line numbers',
406 },
392 help_index : 'ga',
407 'ctrl-k' : {
393 handler : function (event) {
408 help : 'move cell up',
394 IPython.notebook.cell_toggle_line_numbers();
409 help_index : 'ea',
395 return false;
410 handler : function (event) {
396 }
411 that.notebook.move_cell_up();
397 },
412 return false;
398 'i' : {
413 }
399 help : 'interrupt kernel (press twice)',
414 },
400 help_index : 'ha',
415 'l' : {
401 count: 2,
416 help : 'toggle line numbers',
402 handler : function (event) {
417 help_index : 'ga',
403 IPython.notebook.kernel.interrupt();
418 handler : function (event) {
404 return false;
419 that.notebook.cell_toggle_line_numbers();
405 }
420 return false;
406 },
421 }
407 '0' : {
422 },
408 help : 'restart kernel (press twice)',
423 'i' : {
409 help_index : 'hb',
424 help : 'interrupt kernel (press twice)',
410 count: 2,
425 help_index : 'ha',
411 handler : function (event) {
426 count: 2,
412 IPython.notebook.restart_kernel();
427 handler : function (event) {
413 return false;
428 that.notebook.kernel.interrupt();
414 }
429 return false;
415 },
430 }
416 'h' : {
431 },
417 help : 'keyboard shortcuts',
432 '0' : {
418 help_index : 'ge',
433 help : 'restart kernel (press twice)',
419 handler : function (event) {
434 help_index : 'hb',
420 IPython.quick_help.show_keyboard_shortcuts();
435 count: 2,
421 return false;
436 handler : function (event) {
422 }
437 that.notebook.restart_kernel();
423 },
438 return false;
424 'z' : {
439 }
425 help : 'undo last delete',
440 },
426 help_index : 'ei',
441 'h' : {
427 handler : function (event) {
442 help : 'keyboard shortcuts',
428 IPython.notebook.undelete_cell();
443 help_index : 'ge',
429 return false;
444 handler : function (event) {
430 }
445 that.quick_help.show_keyboard_shortcuts();
431 },
446 return false;
432 'shift-m' : {
447 }
433 help : 'merge cell below',
448 },
434 help_index : 'ek',
449 'z' : {
435 handler : function (event) {
450 help : 'undo last delete',
436 IPython.notebook.merge_cell_below();
451 help_index : 'ei',
437 return false;
452 handler : function (event) {
438 }
453 that.notebook.undelete_cell();
439 },
454 return false;
440 'q' : {
455 }
441 help : 'close pager',
456 },
442 help_index : 'gd',
457 'shift-m' : {
443 handler : function (event) {
458 help : 'merge cell below',
444 IPython.pager.collapse();
459 help_index : 'ek',
445 return false;
460 handler : function (event) {
446 }
461 that.notebook.merge_cell_below();
447 },
462 return false;
448 };
463 }
449
464 },
450
465 'q' : {
451 // Main keyboard manager for the notebook
466 help : 'close pager',
452
467 help_index : 'gd',
453 var ShortcutManager = IPython.keyboard.ShortcutManager;
468 handler : function (event) {
454 var keycodes = IPython.keyboard.keycodes;
469 that.pager.collapse();
455
470 return false;
456 var KeyboardManager = function () {
471 }
457 this.mode = 'command';
472 },
458 this.enabled = true;
473 };
459 this.bind_events();
460 this.command_shortcuts = new ShortcutManager();
461 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
462 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
463 this.edit_shortcuts = new ShortcutManager();
464 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
465 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
466 };
474 };
467
475
468 KeyboardManager.prototype.bind_events = function () {
476 KeyboardManager.prototype.bind_events = function () {
@@ -473,7 +481,7 b' var IPython = (function (IPython) {'
473 };
481 };
474
482
475 KeyboardManager.prototype.handle_keydown = function (event) {
483 KeyboardManager.prototype.handle_keydown = function (event) {
476 var notebook = IPython.notebook;
484 var notebook = this.notebook;
477
485
478 if (event.which === keycodes.esc) {
486 if (event.which === keycodes.esc) {
479 // Intercept escape at highest level to avoid closing
487 // Intercept escape at highest level to avoid closing
@@ -545,18 +553,14 b' var IPython = (function (IPython) {'
545 // is_focused must be used to check for the case where an element within
553 // is_focused must be used to check for the case where an element within
546 // the element being removed is focused.
554 // the element being removed is focused.
547 e.on('remove', function () {
555 e.on('remove', function () {
548 if (IPython.utils.is_focused(e[0])) {
556 if (utils.is_focused(e[0])) {
549 that.enable();
557 that.enable();
550 }
558 }
551 });
559 });
552 };
560 };
553
561
554
562 // For backwards compatability.
555 IPython.default_common_shortcuts = default_common_shortcuts;
556 IPython.default_edit_shortcuts = default_edit_shortcuts;
557 IPython.default_command_shortcuts = default_command_shortcuts;
558 IPython.KeyboardManager = KeyboardManager;
563 IPython.KeyboardManager = KeyboardManager;
559
564
560 return IPython;
565 return {'KeyboardManager': KeyboardManager};
561
566 });
562 }(IPython));
@@ -1,19 +1,15 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 ], function(IPython, $) {
8 //============================================================================
9 // Layout
10 //============================================================================
11
12 var IPython = (function (IPython) {
13 "use strict";
8 "use strict";
14
9
15 var LayoutManager = function () {
10 var LayoutManager = function () {
16 this.bind_events();
11 this.bind_events();
12 this.pager = undefined;
17 };
13 };
18
14
19 LayoutManager.prototype.bind_events = function () {
15 LayoutManager.prototype.bind_events = function () {
@@ -43,19 +39,20 b' var IPython = (function (IPython) {'
43 var app_height = this.app_height(); // content height
39 var app_height = this.app_height(); // content height
44
40
45 $('#ipython-main-app').height(app_height); // content+padding+border height
41 $('#ipython-main-app').height(app_height); // content+padding+border height
46
42 if (this.pager) {
47 var pager_height = IPython.pager.percentage_height*app_height;
43 var pager_height = this.pager.percentage_height*app_height;
48 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
44 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
49 $('div#pager').outerHeight(pager_height);
45 $('div#pager').outerHeight(pager_height);
50 if (IPython.pager.expanded) {
46 if (this.pager.expanded) {
51 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
47 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
52 } else {
48 } else {
53 $('div#notebook').outerHeight(app_height-pager_splitter_height);
49 $('div#notebook').outerHeight(app_height-pager_splitter_height);
50 }
54 }
51 }
55 };
52 };
56
53
54 // Backwards compatability.
57 IPython.LayoutManager = LayoutManager;
55 IPython.LayoutManager = LayoutManager;
58
56
59 return IPython;
57 return {'LayoutManager': LayoutManager};
60
58 });
61 }(IPython));
@@ -1,78 +1,96 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 require([
9 // On document ready
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 'notebook/js/notebook',
12 // for the time beeing, we have to pass marked as a parameter here,
8 'base/js/utils',
13 // as injecting require.js make marked not to put itself in the globals,
9 'base/js/page',
14 // which make both this file fail at setting marked configuration, and textcell.js
10 'notebook/js/layoutmanager',
15 // which search marked into global.
11 'base/js/events',
16 require(['components/marked/lib/marked',
12 'auth/js/loginwidget',
17 'widgets/js/init',
13 'notebook/js/maintoolbar',
18 'components/bootstrap-tour/build/js/bootstrap-tour.min'],
14 'notebook/js/pager',
19
15 'notebook/js/quickhelp',
20 function (marked) {
16 'notebook/js/menubar',
17 'notebook/js/notificationarea',
18 'notebook/js/savewidget',
19 'notebook/js/keyboardmanager',
20 'notebook/js/config',
21 'notebook/js/kernelselector',
22 // only loaded, not used:
23 'custom/custom',
24 ], function(
25 IPython,
26 $,
27 notebook,
28 utils,
29 page,
30 layoutmanager,
31 events,
32 loginwidget,
33 maintoolbar,
34 pager,
35 quickhelp,
36 menubar,
37 notificationarea,
38 savewidget,
39 keyboardmanager,
40 config,
41 kernelselector
42 ) {
21 "use strict";
43 "use strict";
22
44
23 window.marked = marked;
45 var common_options = {
24
46 base_url : utils.get_body_data("baseUrl"),
25 // monkey patch CM to be able to syntax highlight cell magics
47 ws_url : IPython.utils.get_body_data("wsUrl"),
26 // bug reported upstream,
48 notebook_path : utils.get_body_data("notebookPath"),
27 // see https://github.com/marijnh/CodeMirror2/issues/670
49 notebook_name : utils.get_body_data('notebookName')
28 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
29 console.log('patching CM for undefined indent');
30 CodeMirror.modes.null = function() {
31 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
32 };
33 }
34
35 CodeMirror.patchedGetMode = function(config, mode){
36 var cmmode = CodeMirror.getMode(config, mode);
37 if(cmmode.indent === null) {
38 console.log('patch mode "' , mode, '" on the fly');
39 cmmode.indent = function(){return 0;};
40 }
41 return cmmode;
42 };
43 // end monkey patching CodeMirror
44
45 IPython.mathjaxutils.init();
46
47 $('#ipython-main-app').addClass('border-box-sizing');
48 $('div#notebook_panel').addClass('border-box-sizing');
49
50 var opts = {
51 base_url : IPython.utils.get_body_data("baseUrl"),
52 notebook_path : IPython.utils.get_body_data("notebookPath"),
53 notebook_name : IPython.utils.get_body_data('notebookName')
54 };
50 };
55
51
56 IPython.page = new IPython.Page();
52 var user_config = $.extend({}, config.default_config);
57 IPython.layout_manager = new IPython.LayoutManager();
53 var page = new page.Page();
58 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
54 var layout_manager = new layoutmanager.LayoutManager();
59 IPython.quick_help = new IPython.QuickHelp();
55 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
60 try {
56 layout_manager: layout_manager,
61 IPython.tour = new IPython.NotebookTour();
57 events: events});
62 } catch (e) {
58 var keyboard_manager = new keyboardmanager.KeyboardManager({
63 console.log("Failed to instantiate Notebook Tour", e);
59 pager: pager,
64 }
60 events: events});
65 IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts);
61 var save_widget = new savewidget.SaveWidget('span#save_widget', {
66 IPython.notebook = new IPython.Notebook('div#notebook', opts);
62 events: events,
67 IPython.keyboard_manager = new IPython.KeyboardManager();
63 keyboard_manager: keyboard_manager});
68 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
64 var notebook = new notebook.Notebook('div#notebook', $.extend({
69 IPython.menubar = new IPython.MenuBar('#menubar', opts);
65 events: events,
70 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container');
66 keyboard_manager: keyboard_manager,
71 IPython.tooltip = new IPython.Tooltip();
67 save_widget: save_widget,
72 IPython.notification_area = new IPython.NotificationArea('#notification_area');
68 config: user_config},
73 IPython.notification_area.init_notification_widgets();
69 common_options));
74
70 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
75 IPython.layout_manager.do_resize();
71 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
72 notebook: notebook,
73 events: events});
74 var quick_help = new quickhelp.QuickHelp({
75 keyboard_manager: keyboard_manager,
76 events: events,
77 notebook: notebook});
78 var menubar = new menubar.MenuBar('#menubar', $.extend({
79 notebook: notebook,
80 layout_manager: layout_manager,
81 events: events,
82 save_widget: save_widget,
83 quick_help: quick_help},
84 common_options));
85 var notification_area = new notificationarea.NotificationArea(
86 '#notification_area', {
87 events: events,
88 save_widget: save_widget,
89 notebook: notebook,
90 keyboard_manager: keyboard_manager});
91 notification_area.init_notification_widgets();
92 var kernel_selector = new kernelselector.KernelSelector(
93 '#kernel_selector_widget', notebook);
76
94
77 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
95 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
78 '<span id="test2" style="font-weight: bold;">x</span>'+
96 '<span id="test2" style="font-weight: bold;">x</span>'+
@@ -85,43 +103,37 b' function (marked) {'
85 }
103 }
86 $('#fonttest').remove();
104 $('#fonttest').remove();
87
105
88 IPython.page.show();
106 page.show();
89
107
90 IPython.layout_manager.do_resize();
108 layout_manager.do_resize();
91 var first_load = function () {
109 var first_load = function () {
92 IPython.layout_manager.do_resize();
110 layout_manager.do_resize();
93 var hash = document.location.hash;
111 var hash = document.location.hash;
94 if (hash) {
112 if (hash) {
95 document.location.hash = '';
113 document.location.hash = '';
96 document.location.hash = hash;
114 document.location.hash = hash;
97 }
115 }
98 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
116 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
99 // only do this once
117 // only do this once
100 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
118 events.off('notebook_loaded.Notebook', first_load);
101 };
119 };
120 events.on('notebook_loaded.Notebook', first_load);
102
121
103 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
122 IPython.page = page;
104 $([IPython.events]).trigger('app_initialized.NotebookApp');
123 IPython.layout_manager = layout_manager;
105 IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path);
124 IPython.notebook = notebook;
125 IPython.pager = pager;
126 IPython.quick_help = quick_help;
127 IPython.login_widget = login_widget;
128 IPython.menubar = menubar;
129 IPython.toolbar = toolbar;
130 IPython.notification_area = notification_area;
131 IPython.keyboard_manager = keyboard_manager;
132 IPython.save_widget = save_widget;
133 IPython.config = user_config;
134 IPython.tooltip = notebook.tooltip;
135
136 events.trigger('app_initialized.NotebookApp');
137 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
106
138
107 if (marked) {
108 marked.setOptions({
109 gfm : true,
110 tables: true,
111 langPrefix: "language-",
112 highlight: function(code, lang) {
113 if (!lang) {
114 // no language, no highlight
115 return code;
116 }
117 var highlighted;
118 try {
119 highlighted = hljs.highlight(lang, code, false);
120 } catch(err) {
121 highlighted = hljs.highlightAuto(code);
122 }
123 return highlighted.value;
124 }
125 });
126 }
127 });
139 });
@@ -1,35 +1,43 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 'notebook/js/toolbar',
8 //============================================================================
8 'notebook/js/celltoolbar',
9 // ToolBar
9 ], function(IPython, $, toolbar, celltoolbar) {
10 //============================================================================
11
12 var IPython = (function (IPython) {
13 "use strict";
10 "use strict";
14
11
15 var MainToolBar = function (selector) {
12 var MainToolBar = function (selector, options) {
16 IPython.ToolBar.apply(this, arguments);
13 // Constructor
14 //
15 // Parameters:
16 // selector: string
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // events: $(Events) instance
20 // notebook: Notebook instance
21 toolbar.ToolBar.apply(this, arguments);
22 this.events = options.events;
23 this.notebook = options.notebook;
17 this.construct();
24 this.construct();
18 this.add_celltype_list();
25 this.add_celltype_list();
19 this.add_celltoolbar_list();
26 this.add_celltoolbar_list();
20 this.bind_events();
27 this.bind_events();
21 };
28 };
22
29
23 MainToolBar.prototype = new IPython.ToolBar();
30 MainToolBar.prototype = new toolbar.ToolBar();
24
31
25 MainToolBar.prototype.construct = function () {
32 MainToolBar.prototype.construct = function () {
33 var that = this;
26 this.add_buttons_group([
34 this.add_buttons_group([
27 {
35 {
28 id : 'save_b',
36 id : 'save_b',
29 label : 'Save and Checkpoint',
37 label : 'Save and Checkpoint',
30 icon : 'icon-save',
38 icon : 'fa-save',
31 callback : function () {
39 callback : function () {
32 IPython.notebook.save_checkpoint();
40 that.notebook.save_checkpoint();
33 }
41 }
34 }
42 }
35 ]);
43 ]);
@@ -38,11 +46,11 b' var IPython = (function (IPython) {'
38 {
46 {
39 id : 'insert_below_b',
47 id : 'insert_below_b',
40 label : 'Insert Cell Below',
48 label : 'Insert Cell Below',
41 icon : 'icon-plus-sign',
49 icon : 'fa-plus',
42 callback : function () {
50 callback : function () {
43 IPython.notebook.insert_cell_below('code');
51 that.notebook.insert_cell_below('code');
44 IPython.notebook.select_next();
52 that.notebook.select_next();
45 IPython.notebook.focus_cell();
53 that.notebook.focus_cell();
46 }
54 }
47 }
55 }
48 ],'insert_above_below');
56 ],'insert_above_below');
@@ -51,25 +59,25 b' var IPython = (function (IPython) {'
51 {
59 {
52 id : 'cut_b',
60 id : 'cut_b',
53 label : 'Cut Cell',
61 label : 'Cut Cell',
54 icon : 'icon-cut',
62 icon : 'fa-cut',
55 callback : function () {
63 callback : function () {
56 IPython.notebook.cut_cell();
64 that.notebook.cut_cell();
57 }
65 }
58 },
66 },
59 {
67 {
60 id : 'copy_b',
68 id : 'copy_b',
61 label : 'Copy Cell',
69 label : 'Copy Cell',
62 icon : 'icon-copy',
70 icon : 'fa-copy',
63 callback : function () {
71 callback : function () {
64 IPython.notebook.copy_cell();
72 that.notebook.copy_cell();
65 }
73 }
66 },
74 },
67 {
75 {
68 id : 'paste_b',
76 id : 'paste_b',
69 label : 'Paste Cell Below',
77 label : 'Paste Cell Below',
70 icon : 'icon-paste',
78 icon : 'fa-paste',
71 callback : function () {
79 callback : function () {
72 IPython.notebook.paste_cell_below();
80 that.notebook.paste_cell_below();
73 }
81 }
74 }
82 }
75 ],'cut_copy_paste');
83 ],'cut_copy_paste');
@@ -78,17 +86,17 b' var IPython = (function (IPython) {'
78 {
86 {
79 id : 'move_up_b',
87 id : 'move_up_b',
80 label : 'Move Cell Up',
88 label : 'Move Cell Up',
81 icon : 'icon-arrow-up',
89 icon : 'fa-arrow-up',
82 callback : function () {
90 callback : function () {
83 IPython.notebook.move_cell_up();
91 that.notebook.move_cell_up();
84 }
92 }
85 },
93 },
86 {
94 {
87 id : 'move_down_b',
95 id : 'move_down_b',
88 label : 'Move Cell Down',
96 label : 'Move Cell Down',
89 icon : 'icon-arrow-down',
97 icon : 'fa-arrow-down',
90 callback : function () {
98 callback : function () {
91 IPython.notebook.move_cell_down();
99 that.notebook.move_cell_down();
92 }
100 }
93 }
101 }
94 ],'move_up_down');
102 ],'move_up_down');
@@ -98,26 +106,26 b' var IPython = (function (IPython) {'
98 {
106 {
99 id : 'run_b',
107 id : 'run_b',
100 label : 'Run Cell',
108 label : 'Run Cell',
101 icon : 'icon-play',
109 icon : 'fa-play',
102 callback : function () {
110 callback : function () {
103 // emulate default shift-enter behavior
111 // emulate default shift-enter behavior
104 IPython.notebook.execute_cell_and_select_below();
112 that.notebook.execute_cell_and_select_below();
105 }
113 }
106 },
114 },
107 {
115 {
108 id : 'interrupt_b',
116 id : 'interrupt_b',
109 label : 'Interrupt',
117 label : 'Interrupt',
110 icon : 'icon-stop',
118 icon : 'fa-stop',
111 callback : function () {
119 callback : function () {
112 IPython.notebook.session.interrupt_kernel();
120 that.notebook.session.interrupt_kernel();
113 }
121 }
114 },
122 },
115 {
123 {
116 id : 'repeat_b',
124 id : 'repeat_b',
117 label : 'Restart Kernel',
125 label : 'Restart Kernel',
118 icon : 'icon-repeat',
126 icon : 'fa-repeat',
119 callback : function () {
127 callback : function () {
120 IPython.notebook.restart_kernel();
128 that.notebook.restart_kernel();
121 }
129 }
122 }
130 }
123 ],'run_int');
131 ],'run_int');
@@ -150,30 +158,31 b' var IPython = (function (IPython) {'
150 .addClass('form-control select-xs')
158 .addClass('form-control select-xs')
151 .append($('<option/>').attr('value', '').text('None'));
159 .append($('<option/>').attr('value', '').text('None'));
152 this.element.append(label).append(select);
160 this.element.append(label).append(select);
161 var that = this;
153 select.change(function() {
162 select.change(function() {
154 var val = $(this).val()
163 var val = $(this).val();
155 if (val =='') {
164 if (val ==='') {
156 IPython.CellToolbar.global_hide();
165 celltoolbar.CellToolbar.global_hide();
157 delete IPython.notebook.metadata.celltoolbar;
166 delete that.notebook.metadata.celltoolbar;
158 } else {
167 } else {
159 IPython.CellToolbar.global_show();
168 celltoolbar.CellToolbar.global_show();
160 IPython.CellToolbar.activate_preset(val);
169 celltoolbar.CellToolbar.activate_preset(val, that.events);
161 IPython.notebook.metadata.celltoolbar = val;
170 that.notebook.metadata.celltoolbar = val;
162 }
171 }
163 });
172 });
164 // Setup the currently registered presets.
173 // Setup the currently registered presets.
165 var presets = IPython.CellToolbar.list_presets();
174 var presets = celltoolbar.CellToolbar.list_presets();
166 for (var i=0; i<presets.length; i++) {
175 for (var i=0; i<presets.length; i++) {
167 var name = presets[i];
176 var name = presets[i];
168 select.append($('<option/>').attr('value', name).text(name));
177 select.append($('<option/>').attr('value', name).text(name));
169 }
178 }
170 // Setup future preset registrations.
179 // Setup future preset registrations.
171 $([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
180 this.events.on('preset_added.CellToolbar', function (event, data) {
172 var name = data.name;
181 var name = data.name;
173 select.append($('<option/>').attr('value', name).text(name));
182 select.append($('<option/>').attr('value', name).text(name));
174 });
183 });
175 // Update select value when a preset is activated.
184 // Update select value when a preset is activated.
176 $([IPython.events]).on('preset_activated.CellToolbar', function (event, data) {
185 this.events.on('preset_activated.CellToolbar', function (event, data) {
177 if (select.val() !== data.name)
186 if (select.val() !== data.name)
178 select.val(data.name);
187 select.val(data.name);
179 });
188 });
@@ -186,26 +195,26 b' var IPython = (function (IPython) {'
186 this.element.find('#cell_type').change(function () {
195 this.element.find('#cell_type').change(function () {
187 var cell_type = $(this).val();
196 var cell_type = $(this).val();
188 if (cell_type === 'code') {
197 if (cell_type === 'code') {
189 IPython.notebook.to_code();
198 that.notebook.to_code();
190 } else if (cell_type === 'markdown') {
199 } else if (cell_type === 'markdown') {
191 IPython.notebook.to_markdown();
200 that.notebook.to_markdown();
192 } else if (cell_type === 'raw') {
201 } else if (cell_type === 'raw') {
193 IPython.notebook.to_raw();
202 that.notebook.to_raw();
194 } else if (cell_type === 'heading1') {
203 } else if (cell_type === 'heading1') {
195 IPython.notebook.to_heading(undefined, 1);
204 that.notebook.to_heading(undefined, 1);
196 } else if (cell_type === 'heading2') {
205 } else if (cell_type === 'heading2') {
197 IPython.notebook.to_heading(undefined, 2);
206 that.notebook.to_heading(undefined, 2);
198 } else if (cell_type === 'heading3') {
207 } else if (cell_type === 'heading3') {
199 IPython.notebook.to_heading(undefined, 3);
208 that.notebook.to_heading(undefined, 3);
200 } else if (cell_type === 'heading4') {
209 } else if (cell_type === 'heading4') {
201 IPython.notebook.to_heading(undefined, 4);
210 that.notebook.to_heading(undefined, 4);
202 } else if (cell_type === 'heading5') {
211 } else if (cell_type === 'heading5') {
203 IPython.notebook.to_heading(undefined, 5);
212 that.notebook.to_heading(undefined, 5);
204 } else if (cell_type === 'heading6') {
213 } else if (cell_type === 'heading6') {
205 IPython.notebook.to_heading(undefined, 6);
214 that.notebook.to_heading(undefined, 6);
206 }
215 }
207 });
216 });
208 $([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
217 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
209 if (data.cell_type === 'heading') {
218 if (data.cell_type === 'heading') {
210 that.element.find('#cell_type').val(data.cell_type+data.level);
219 that.element.find('#cell_type').val(data.cell_type+data.level);
211 } else {
220 } else {
@@ -214,8 +223,8 b' var IPython = (function (IPython) {'
214 });
223 });
215 };
224 };
216
225
226 // Backwards compatability.
217 IPython.MainToolBar = MainToolBar;
227 IPython.MainToolBar = MainToolBar;
218
228
219 return IPython;
229 return {'MainToolBar': MainToolBar};
220
230 });
221 }(IPython));
@@ -1,18 +1,12 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 'base/js/utils',
8 //============================================================================
8 'base/js/dialog',
9 // MathJax utility functions
9 ], function(IPython, $, utils, dialog) {
10 //============================================================================
11
12
13 IPython.namespace('IPython.mathjaxutils');
14
15 IPython.mathjaxutils = (function (IPython) {
16 "use strict";
10 "use strict";
17
11
18 var init = function () {
12 var init = function () {
@@ -75,7 +69,7 b' IPython.mathjaxutils = (function (IPython) {'
75 "which will prevent this dialog from appearing."
69 "which will prevent this dialog from appearing."
76 )
70 )
77 );
71 );
78 IPython.dialog.modal({
72 dialog.modal({
79 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
73 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
80 body : message,
74 body : message,
81 buttons : {
75 buttons : {
@@ -110,7 +104,7 b' IPython.mathjaxutils = (function (IPython) {'
110 .replace(/</g, "&lt;") // use HTML entity for <
104 .replace(/</g, "&lt;") // use HTML entity for <
111 .replace(/>/g, "&gt;") // use HTML entity for >
105 .replace(/>/g, "&gt;") // use HTML entity for >
112 ;
106 ;
113 if (IPython.utils.browser === 'msie') {
107 if (utils.browser === 'msie') {
114 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
108 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
115 }
109 }
116 while (j > i) {
110 while (j > i) {
@@ -159,7 +153,7 b' IPython.mathjaxutils = (function (IPython) {'
159 de_tilde = function (text) { return text; };
153 de_tilde = function (text) { return text; };
160 }
154 }
161
155
162 var blocks = IPython.utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
156 var blocks = utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
163
157
164 for (var i = 1, m = blocks.length; i < m; i += 2) {
158 for (var i = 1, m = blocks.length; i < m; i += 2) {
165 var block = blocks[i];
159 var block = blocks[i];
@@ -242,10 +236,13 b' IPython.mathjaxutils = (function (IPython) {'
242 return text;
236 return text;
243 };
237 };
244
238
245 return {
239 var mathjaxutils = {
246 init : init,
240 init : init,
247 remove_math : remove_math,
241 remove_math : remove_math,
248 replace_math : replace_math
242 replace_math : replace_math
249 };
243 };
250
244
251 }(IPython));
245 IPython.mathjaxutils = mathjaxutils;
246
247 return mathjaxutils;
248 });
@@ -1,40 +1,49 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 //============================================================================
4 define([
5 // MenuBar
5 'base/js/namespace',
6 //============================================================================
6 'jquery',
7
7 'base/js/utils',
8 /**
8 'notebook/js/tour',
9 * @module IPython
9 'bootstrap',
10 * @namespace IPython
10 'moment',
11 * @submodule MenuBar
11 ], function(IPython, $, utils, tour, bootstrap, moment) {
12 */
13
14
15 var IPython = (function (IPython) {
16 "use strict";
12 "use strict";
17
13
18 var utils = IPython.utils;
19
20 /**
21 * A MenuBar Class to generate the menubar of IPython notebook
22 * @Class MenuBar
23 *
24 * @constructor
25 *
26 *
27 * @param selector {string} selector for the menubar element in DOM
28 * @param {object} [options]
29 * @param [options.base_url] {String} String to use for the
30 * base project url. Default is to inspect
31 * $('body').data('baseUrl');
32 * does not support change for now is set through this option
33 */
34 var MenuBar = function (selector, options) {
14 var MenuBar = function (selector, options) {
15 // Constructor
16 //
17 // A MenuBar Class to generate the menubar of IPython notebook
18 //
19 // Parameters:
20 // selector: string
21 // options: dictionary
22 // Dictionary of keyword arguments.
23 // notebook: Notebook instance
24 // layout_manager: LayoutManager instance
25 // events: $(Events) instance
26 // save_widget: SaveWidget instance
27 // quick_help: QuickHelp instance
28 // base_url : string
29 // notebook_path : string
30 // notebook_name : string
35 options = options || {};
31 options = options || {};
36 this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl");
32 this.base_url = options.base_url || utils.get_body_data("baseUrl");
37 this.selector = selector;
33 this.selector = selector;
34 this.notebook = options.notebook;
35 this.layout_manager = options.layout_manager;
36 this.events = options.events;
37 this.save_widget = options.save_widget;
38 this.quick_help = options.quick_help;
39
40 try {
41 this.tour = new tour.Tour(this.notebook, this.events);
42 } catch (e) {
43 this.tour = undefined;
44 console.log("Failed to instantiate Notebook Tour", e);
45 }
46
38 if (this.selector !== undefined) {
47 if (this.selector !== undefined) {
39 this.element = $(selector);
48 this.element = $(selector);
40 this.style();
49 this.style();
@@ -42,23 +51,24 b' var IPython = (function (IPython) {'
42 }
51 }
43 };
52 };
44
53
54 // TODO: This has definitively nothing to do with style ...
45 MenuBar.prototype.style = function () {
55 MenuBar.prototype.style = function () {
46 this.element.addClass('border-box-sizing');
56 var that = this;
47 this.element.find("li").click(function (event, ui) {
57 this.element.find("li").click(function (event, ui) {
48 // The selected cell loses focus when the menu is entered, so we
58 // The selected cell loses focus when the menu is entered, so we
49 // re-select it upon selection.
59 // re-select it upon selection.
50 var i = IPython.notebook.get_selected_index();
60 var i = that.notebook.get_selected_index();
51 IPython.notebook.select(i);
61 that.notebook.select(i);
52 }
62 }
53 );
63 );
54 };
64 };
55
65
56 MenuBar.prototype._nbconvert = function (format, download) {
66 MenuBar.prototype._nbconvert = function (format, download) {
57 download = download || false;
67 download = download || false;
58 var notebook_path = IPython.notebook.notebook_path;
68 var notebook_path = this.notebook.notebook_path;
59 var notebook_name = IPython.notebook.notebook_name;
69 var notebook_name = this.notebook.notebook_name;
60 if (IPython.notebook.dirty) {
70 if (this.notebook.dirty) {
61 IPython.notebook.save_notebook({async : false});
71 this.notebook.save_notebook({async : false});
62 }
72 }
63 var url = utils.url_join_encode(
73 var url = utils.url_join_encode(
64 this.base_url,
74 this.base_url,
@@ -75,25 +85,25 b' var IPython = (function (IPython) {'
75 // File
85 // File
76 var that = this;
86 var that = this;
77 this.element.find('#new_notebook').click(function () {
87 this.element.find('#new_notebook').click(function () {
78 IPython.notebook.new_notebook();
88 that.notebook.new_notebook();
79 });
89 });
80 this.element.find('#open_notebook').click(function () {
90 this.element.find('#open_notebook').click(function () {
81 window.open(utils.url_join_encode(
91 window.open(utils.url_join_encode(
82 IPython.notebook.base_url,
92 that.notebook.base_url,
83 'tree',
93 'tree',
84 IPython.notebook.notebook_path
94 that.notebook.notebook_path
85 ));
95 ));
86 });
96 });
87 this.element.find('#copy_notebook').click(function () {
97 this.element.find('#copy_notebook').click(function () {
88 IPython.notebook.copy_notebook();
98 that.notebook.copy_notebook();
89 return false;
99 return false;
90 });
100 });
91 this.element.find('#download_ipynb').click(function () {
101 this.element.find('#download_ipynb').click(function () {
92 var base_url = IPython.notebook.base_url;
102 var base_url = that.notebook.base_url;
93 var notebook_path = IPython.notebook.notebook_path;
103 var notebook_path = that.notebook.notebook_path;
94 var notebook_name = IPython.notebook.notebook_name;
104 var notebook_name = that.notebook.notebook_name;
95 if (IPython.notebook.dirty) {
105 if (that.notebook.dirty) {
96 IPython.notebook.save_notebook({async : false});
106 that.notebook.save_notebook({async : false});
97 }
107 }
98
108
99 var url = utils.url_join_encode(
109 var url = utils.url_join_encode(
@@ -126,17 +136,17 b' var IPython = (function (IPython) {'
126 });
136 });
127
137
128 this.element.find('#rename_notebook').click(function () {
138 this.element.find('#rename_notebook').click(function () {
129 IPython.save_widget.rename_notebook();
139 that.save_widget.rename_notebook({notebook: that.notebook});
130 });
140 });
131 this.element.find('#save_checkpoint').click(function () {
141 this.element.find('#save_checkpoint').click(function () {
132 IPython.notebook.save_checkpoint();
142 that.notebook.save_checkpoint();
133 });
143 });
134 this.element.find('#restore_checkpoint').click(function () {
144 this.element.find('#restore_checkpoint').click(function () {
135 });
145 });
136 this.element.find('#trust_notebook').click(function () {
146 this.element.find('#trust_notebook').click(function () {
137 IPython.notebook.trust_notebook();
147 that.notebook.trust_notebook();
138 });
148 });
139 $([IPython.events]).on('trust_changed.Notebook', function (event, trusted) {
149 this.events.on('trust_changed.Notebook', function (event, trusted) {
140 if (trusted) {
150 if (trusted) {
141 that.element.find('#trust_notebook')
151 that.element.find('#trust_notebook')
142 .addClass("disabled")
152 .addClass("disabled")
@@ -148,157 +158,160 b' var IPython = (function (IPython) {'
148 }
158 }
149 });
159 });
150 this.element.find('#kill_and_exit').click(function () {
160 this.element.find('#kill_and_exit').click(function () {
151 IPython.notebook.session.delete();
161 var close_window = function () {
152 setTimeout(function(){
153 // allow closing of new tabs in Chromium, impossible in FF
162 // allow closing of new tabs in Chromium, impossible in FF
154 window.open('', '_self', '');
163 window.open('', '_self', '');
155 window.close();
164 window.close();
156 }, 500);
165 };
166 // finish with close on success or failure
167 that.notebook.session.delete(close_window, close_window);
157 });
168 });
158 // Edit
169 // Edit
159 this.element.find('#cut_cell').click(function () {
170 this.element.find('#cut_cell').click(function () {
160 IPython.notebook.cut_cell();
171 that.notebook.cut_cell();
161 });
172 });
162 this.element.find('#copy_cell').click(function () {
173 this.element.find('#copy_cell').click(function () {
163 IPython.notebook.copy_cell();
174 that.notebook.copy_cell();
164 });
175 });
165 this.element.find('#delete_cell').click(function () {
176 this.element.find('#delete_cell').click(function () {
166 IPython.notebook.delete_cell();
177 that.notebook.delete_cell();
167 });
178 });
168 this.element.find('#undelete_cell').click(function () {
179 this.element.find('#undelete_cell').click(function () {
169 IPython.notebook.undelete_cell();
180 that.notebook.undelete_cell();
170 });
181 });
171 this.element.find('#split_cell').click(function () {
182 this.element.find('#split_cell').click(function () {
172 IPython.notebook.split_cell();
183 that.notebook.split_cell();
173 });
184 });
174 this.element.find('#merge_cell_above').click(function () {
185 this.element.find('#merge_cell_above').click(function () {
175 IPython.notebook.merge_cell_above();
186 that.notebook.merge_cell_above();
176 });
187 });
177 this.element.find('#merge_cell_below').click(function () {
188 this.element.find('#merge_cell_below').click(function () {
178 IPython.notebook.merge_cell_below();
189 that.notebook.merge_cell_below();
179 });
190 });
180 this.element.find('#move_cell_up').click(function () {
191 this.element.find('#move_cell_up').click(function () {
181 IPython.notebook.move_cell_up();
192 that.notebook.move_cell_up();
182 });
193 });
183 this.element.find('#move_cell_down').click(function () {
194 this.element.find('#move_cell_down').click(function () {
184 IPython.notebook.move_cell_down();
195 that.notebook.move_cell_down();
185 });
196 });
186 this.element.find('#edit_nb_metadata').click(function () {
197 this.element.find('#edit_nb_metadata').click(function () {
187 IPython.notebook.edit_metadata();
198 that.notebook.edit_metadata({
199 notebook: that.notebook,
200 keyboard_manager: that.notebook.keyboard_manager});
188 });
201 });
189
202
190 // View
203 // View
191 this.element.find('#toggle_header').click(function () {
204 this.element.find('#toggle_header').click(function () {
192 $('div#header').toggle();
205 $('div#header').toggle();
193 IPython.layout_manager.do_resize();
206 that.layout_manager.do_resize();
194 });
207 });
195 this.element.find('#toggle_toolbar').click(function () {
208 this.element.find('#toggle_toolbar').click(function () {
196 $('div#maintoolbar').toggle();
209 $('div#maintoolbar').toggle();
197 IPython.layout_manager.do_resize();
210 that.layout_manager.do_resize();
198 });
211 });
199 // Insert
212 // Insert
200 this.element.find('#insert_cell_above').click(function () {
213 this.element.find('#insert_cell_above').click(function () {
201 IPython.notebook.insert_cell_above('code');
214 that.notebook.insert_cell_above('code');
202 IPython.notebook.select_prev();
215 that.notebook.select_prev();
203 });
216 });
204 this.element.find('#insert_cell_below').click(function () {
217 this.element.find('#insert_cell_below').click(function () {
205 IPython.notebook.insert_cell_below('code');
218 that.notebook.insert_cell_below('code');
206 IPython.notebook.select_next();
219 that.notebook.select_next();
207 });
220 });
208 // Cell
221 // Cell
209 this.element.find('#run_cell').click(function () {
222 this.element.find('#run_cell').click(function () {
210 IPython.notebook.execute_cell();
223 that.notebook.execute_cell();
211 });
224 });
212 this.element.find('#run_cell_select_below').click(function () {
225 this.element.find('#run_cell_select_below').click(function () {
213 IPython.notebook.execute_cell_and_select_below();
226 that.notebook.execute_cell_and_select_below();
214 });
227 });
215 this.element.find('#run_cell_insert_below').click(function () {
228 this.element.find('#run_cell_insert_below').click(function () {
216 IPython.notebook.execute_cell_and_insert_below();
229 that.notebook.execute_cell_and_insert_below();
217 });
230 });
218 this.element.find('#run_all_cells').click(function () {
231 this.element.find('#run_all_cells').click(function () {
219 IPython.notebook.execute_all_cells();
232 that.notebook.execute_all_cells();
220 });
233 });
221 this.element.find('#run_all_cells_above').click(function () {
234 this.element.find('#run_all_cells_above').click(function () {
222 IPython.notebook.execute_cells_above();
235 that.notebook.execute_cells_above();
223 });
236 });
224 this.element.find('#run_all_cells_below').click(function () {
237 this.element.find('#run_all_cells_below').click(function () {
225 IPython.notebook.execute_cells_below();
238 that.notebook.execute_cells_below();
226 });
239 });
227 this.element.find('#to_code').click(function () {
240 this.element.find('#to_code').click(function () {
228 IPython.notebook.to_code();
241 that.notebook.to_code();
229 });
242 });
230 this.element.find('#to_markdown').click(function () {
243 this.element.find('#to_markdown').click(function () {
231 IPython.notebook.to_markdown();
244 that.notebook.to_markdown();
232 });
245 });
233 this.element.find('#to_raw').click(function () {
246 this.element.find('#to_raw').click(function () {
234 IPython.notebook.to_raw();
247 that.notebook.to_raw();
235 });
248 });
236 this.element.find('#to_heading1').click(function () {
249 this.element.find('#to_heading1').click(function () {
237 IPython.notebook.to_heading(undefined, 1);
250 that.notebook.to_heading(undefined, 1);
238 });
251 });
239 this.element.find('#to_heading2').click(function () {
252 this.element.find('#to_heading2').click(function () {
240 IPython.notebook.to_heading(undefined, 2);
253 that.notebook.to_heading(undefined, 2);
241 });
254 });
242 this.element.find('#to_heading3').click(function () {
255 this.element.find('#to_heading3').click(function () {
243 IPython.notebook.to_heading(undefined, 3);
256 that.notebook.to_heading(undefined, 3);
244 });
257 });
245 this.element.find('#to_heading4').click(function () {
258 this.element.find('#to_heading4').click(function () {
246 IPython.notebook.to_heading(undefined, 4);
259 that.notebook.to_heading(undefined, 4);
247 });
260 });
248 this.element.find('#to_heading5').click(function () {
261 this.element.find('#to_heading5').click(function () {
249 IPython.notebook.to_heading(undefined, 5);
262 that.notebook.to_heading(undefined, 5);
250 });
263 });
251 this.element.find('#to_heading6').click(function () {
264 this.element.find('#to_heading6').click(function () {
252 IPython.notebook.to_heading(undefined, 6);
265 that.notebook.to_heading(undefined, 6);
253 });
266 });
254
267
255 this.element.find('#toggle_current_output').click(function () {
268 this.element.find('#toggle_current_output').click(function () {
256 IPython.notebook.toggle_output();
269 that.notebook.toggle_output();
257 });
270 });
258 this.element.find('#toggle_current_output_scroll').click(function () {
271 this.element.find('#toggle_current_output_scroll').click(function () {
259 IPython.notebook.toggle_output_scroll();
272 that.notebook.toggle_output_scroll();
260 });
273 });
261 this.element.find('#clear_current_output').click(function () {
274 this.element.find('#clear_current_output').click(function () {
262 IPython.notebook.clear_output();
275 that.notebook.clear_output();
263 });
276 });
264
277
265 this.element.find('#toggle_all_output').click(function () {
278 this.element.find('#toggle_all_output').click(function () {
266 IPython.notebook.toggle_all_output();
279 that.notebook.toggle_all_output();
267 });
280 });
268 this.element.find('#toggle_all_output_scroll').click(function () {
281 this.element.find('#toggle_all_output_scroll').click(function () {
269 IPython.notebook.toggle_all_output_scroll();
282 that.notebook.toggle_all_output_scroll();
270 });
283 });
271 this.element.find('#clear_all_output').click(function () {
284 this.element.find('#clear_all_output').click(function () {
272 IPython.notebook.clear_all_output();
285 that.notebook.clear_all_output();
273 });
286 });
274
287
275 // Kernel
288 // Kernel
276 this.element.find('#int_kernel').click(function () {
289 this.element.find('#int_kernel').click(function () {
277 IPython.notebook.session.interrupt_kernel();
290 that.notebook.session.interrupt_kernel();
278 });
291 });
279 this.element.find('#restart_kernel').click(function () {
292 this.element.find('#restart_kernel').click(function () {
280 IPython.notebook.restart_kernel();
293 that.notebook.restart_kernel();
281 });
294 });
282 // Help
295 // Help
283 if (IPython.tour) {
296 if (this.tour) {
284 this.element.find('#notebook_tour').click(function () {
297 this.element.find('#notebook_tour').click(function () {
285 IPython.tour.start();
298 that.tour.start();
286 });
299 });
287 } else {
300 } else {
288 this.element.find('#notebook_tour').addClass("disabled");
301 this.element.find('#notebook_tour').addClass("disabled");
289 }
302 }
290 this.element.find('#keyboard_shortcuts').click(function () {
303 this.element.find('#keyboard_shortcuts').click(function () {
291 IPython.quick_help.show_keyboard_shortcuts();
304 that.quick_help.show_keyboard_shortcuts();
292 });
305 });
293
306
294 this.update_restore_checkpoint(null);
307 this.update_restore_checkpoint(null);
295
308
296 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
309 this.events.on('checkpoints_listed.Notebook', function (event, data) {
297 that.update_restore_checkpoint(IPython.notebook.checkpoints);
310 that.update_restore_checkpoint(that.notebook.checkpoints);
298 });
311 });
299
312
300 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
313 this.events.on('checkpoint_created.Notebook', function (event, data) {
301 that.update_restore_checkpoint(IPython.notebook.checkpoints);
314 that.update_restore_checkpoint(that.notebook.checkpoints);
302 });
315 });
303 };
316 };
304
317
@@ -317,23 +330,24 b' var IPython = (function (IPython) {'
317 return;
330 return;
318 }
331 }
319
332
333 var that = this;
320 checkpoints.map(function (checkpoint) {
334 checkpoints.map(function (checkpoint) {
321 var d = new Date(checkpoint.last_modified);
335 var d = new Date(checkpoint.last_modified);
322 ul.append(
336 ul.append(
323 $("<li/>").append(
337 $("<li/>").append(
324 $("<a/>")
338 $("<a/>")
325 .attr("href", "#")
339 .attr("href", "#")
326 .text(d.format("mmm dd HH:MM:ss"))
340 .text(moment(d).format("LLLL"))
327 .click(function () {
341 .click(function () {
328 IPython.notebook.restore_checkpoint_dialog(checkpoint);
342 that.notebook.restore_checkpoint_dialog(checkpoint);
329 })
343 })
330 )
344 )
331 );
345 );
332 });
346 });
333 };
347 };
334
348
349 // Backwards compatability.
335 IPython.MenuBar = MenuBar;
350 IPython.MenuBar = MenuBar;
336
351
337 return IPython;
352 return {'MenuBar': MenuBar};
338
353 });
339 }(IPython));
This diff has been collapsed as it changes many lines, (526 lines changed) Show them Hide them
@@ -1,32 +1,99 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7 'base/js/utils',
8 'base/js/dialog',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
11 'services/sessions/js/session',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
14 'highlight',
15 'notebook/js/mathjaxutils',
16 'base/js/keyboard',
17 'notebook/js/tooltip',
18 'notebook/js/celltoolbarpresets/default',
19 'notebook/js/celltoolbarpresets/rawcell',
20 'notebook/js/celltoolbarpresets/slideshow',
21 ], function (
22 IPython,
23 $,
24 utils,
25 dialog,
26 textcell,
27 codecell,
28 session,
29 celltoolbar,
30 marked,
31 hljs,
32 mathjaxutils,
33 keyboard,
34 tooltip,
35 default_celltoolbar,
36 rawcell_celltoolbar,
37 slideshow_celltoolbar
38 ) {
7
39
8 //============================================================================
9 // Notebook
10 //============================================================================
11
12 var IPython = (function (IPython) {
13 "use strict";
14
15 var utils = IPython.utils;
16
17 /**
18 * A notebook contains and manages cells.
19 *
20 * @class Notebook
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
24 */
25 var Notebook = function (selector, options) {
40 var Notebook = function (selector, options) {
26 this.options = options = options || {};
41 // Constructor
42 //
43 // A notebook contains and manages cells.
44 //
45 // Parameters:
46 // selector: string
47 // options: dictionary
48 // Dictionary of keyword arguments.
49 // events: $(Events) instance
50 // keyboard_manager: KeyboardManager instance
51 // save_widget: SaveWidget instance
52 // config: dictionary
53 // base_url : string
54 // notebook_path : string
55 // notebook_name : string
56 this.config = options.config || {};
27 this.base_url = options.base_url;
57 this.base_url = options.base_url;
28 this.notebook_path = options.notebook_path;
58 this.notebook_path = options.notebook_path;
29 this.notebook_name = options.notebook_name;
59 this.notebook_name = options.notebook_name;
60 this.events = options.events;
61 this.keyboard_manager = options.keyboard_manager;
62 this.save_widget = options.save_widget;
63 this.tooltip = new tooltip.Tooltip(this.events);
64 this.ws_url = options.ws_url;
65 this._session_starting = false;
66 // default_kernel_name is a temporary measure while we implement proper
67 // kernel selection and delayed start. Do not rely on it.
68 this.default_kernel_name = 'python';
69 // TODO: This code smells (and the other `= this` line a couple lines down)
70 // We need a better way to deal with circular instance references.
71 this.keyboard_manager.notebook = this;
72 this.save_widget.notebook = this;
73
74 mathjaxutils.init();
75
76 if (marked) {
77 marked.setOptions({
78 gfm : true,
79 tables: true,
80 langPrefix: "language-",
81 highlight: function(code, lang) {
82 if (!lang) {
83 // no language, no highlight
84 return code;
85 }
86 var highlighted;
87 try {
88 highlighted = hljs.highlight(lang, code, false);
89 } catch(err) {
90 highlighted = hljs.highlightAuto(code);
91 }
92 return highlighted.value;
93 }
94 });
95 }
96
30 this.element = $(selector);
97 this.element = $(selector);
31 this.element.scroll();
98 this.element.scroll();
32 this.element.data("notebook", this);
99 this.element.data("notebook", this);
@@ -55,23 +122,20 b' var IPython = (function (IPython) {'
55 this.notebook_name_blacklist_re = /[\/\\:]/;
122 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3; // Increment this when changing the nbformat
123 this.nbformat = 3; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
124 this.nbformat_minor = 0; // Increment this when changing the nbformat
58 this.style();
125 this.codemirror_mode = 'ipython';
59 this.create_elements();
126 this.create_elements();
60 this.bind_events();
127 this.bind_events();
61 this.save_notebook = function() { // don't allow save until notebook_loaded
128 this.save_notebook = function() { // don't allow save until notebook_loaded
62 this.save_notebook_error(null, null, "Load failed, save is disabled");
129 this.save_notebook_error(null, null, "Load failed, save is disabled");
63 };
130 };
64 };
65
131
66 /**
132 // Trigger cell toolbar registration.
67 * Tweak the notebook's CSS style.
133 default_celltoolbar.register(this);
68 *
134 rawcell_celltoolbar.register(this);
69 * @method style
135 slideshow_celltoolbar.register(this);
70 */
71 Notebook.prototype.style = function () {
72 $('div#notebook').addClass('border-box-sizing');
73 };
136 };
74
137
138
75 /**
139 /**
76 * Create an HTML and CSS representation of the notebook.
140 * Create an HTML and CSS representation of the notebook.
77 *
141 *
@@ -102,36 +166,38 b' var IPython = (function (IPython) {'
102 Notebook.prototype.bind_events = function () {
166 Notebook.prototype.bind_events = function () {
103 var that = this;
167 var that = this;
104
168
105 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
169 this.events.on('set_next_input.Notebook', function (event, data) {
106 var index = that.find_cell_index(data.cell);
170 var index = that.find_cell_index(data.cell);
107 var new_cell = that.insert_cell_below('code',index);
171 var new_cell = that.insert_cell_below('code',index);
108 new_cell.set_text(data.text);
172 new_cell.set_text(data.text);
109 that.dirty = true;
173 that.dirty = true;
110 });
174 });
111
175
112 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
176 this.events.on('set_dirty.Notebook', function (event, data) {
113 that.dirty = data.value;
177 that.dirty = data.value;
114 });
178 });
115
179
116 $([IPython.events]).on('trust_changed.Notebook', function (event, data) {
180 this.events.on('trust_changed.Notebook', function (event, data) {
117 that.trusted = data.value;
181 that.trusted = data.value;
118 });
182 });
119
183
120 $([IPython.events]).on('select.Cell', function (event, data) {
184 this.events.on('select.Cell', function (event, data) {
121 var index = that.find_cell_index(data.cell);
185 var index = that.find_cell_index(data.cell);
122 that.select(index);
186 that.select(index);
123 });
187 });
124
188
125 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
189 this.events.on('edit_mode.Cell', function (event, data) {
126 that.handle_edit_mode(data.cell);
190 that.handle_edit_mode(data.cell);
127 });
191 });
128
192
129 $([IPython.events]).on('command_mode.Cell', function (event, data) {
193 this.events.on('command_mode.Cell', function (event, data) {
130 that.handle_command_mode(data.cell);
194 that.handle_command_mode(data.cell);
131 });
195 });
132
196
133 $([IPython.events]).on('status_autorestarting.Kernel', function () {
197 this.events.on('status_autorestarting.Kernel', function () {
134 IPython.dialog.modal({
198 dialog.modal({
199 notebook: that,
200 keyboard_manager: that.keyboard_manager,
135 title: "Kernel Restarting",
201 title: "Kernel Restarting",
136 body: "The kernel appears to have died. It will restart automatically.",
202 body: "The kernel appears to have died. It will restart automatically.",
137 buttons: {
203 buttons: {
@@ -141,6 +207,13 b' var IPython = (function (IPython) {'
141 }
207 }
142 });
208 });
143 });
209 });
210
211 this.events.on('spec_changed.Kernel', function(event, data) {
212 that.set_kernelspec_metadata(data);
213 if (data.codemirror_mode) {
214 that.set_codemirror_mode(data.codemirror_mode);
215 }
216 });
144
217
145 var collapse_time = function (time) {
218 var collapse_time = function (time) {
146 var app_height = $('#ipython-main-app').height(); // content height
219 var app_height = $('#ipython-main-app').height(); // content height
@@ -211,7 +284,7 b' var IPython = (function (IPython) {'
211 if (this.dirty == value) {
284 if (this.dirty == value) {
212 return;
285 return;
213 }
286 }
214 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
287 this.events.trigger('set_dirty.Notebook', {value: value});
215 };
288 };
216
289
217 /**
290 /**
@@ -254,10 +327,25 b' var IPython = (function (IPython) {'
254
327
255 Notebook.prototype.edit_metadata = function () {
328 Notebook.prototype.edit_metadata = function () {
256 var that = this;
329 var that = this;
257 IPython.dialog.edit_metadata(this.metadata, function (md) {
330 dialog.edit_metadata({
258 that.metadata = md;
331 md: this.metadata,
259 }, 'Notebook');
332 callback: function (md) {
333 that.metadata = md;
334 },
335 name: 'Notebook',
336 notebook: this,
337 keyboard_manager: this.keyboard_manager});
260 };
338 };
339
340 Notebook.prototype.set_kernelspec_metadata = function(ks) {
341 var tostore = {};
342 $.map(ks, function(value, field) {
343 if (field !== 'argv' && field !== 'env') {
344 tostore[field] = value;
345 }
346 });
347 this.metadata.kernelspec = tostore;
348 }
261
349
262 // Cell indexing, retrieval, etc.
350 // Cell indexing, retrieval, etc.
263
351
@@ -295,7 +383,7 b' var IPython = (function (IPython) {'
295 * @return {Cell} Cell or null if no cell was found.
383 * @return {Cell} Cell or null if no cell was found.
296 */
384 */
297 Notebook.prototype.get_msg_cell = function (msg_id) {
385 Notebook.prototype.get_msg_cell = function (msg_id) {
298 return IPython.CodeCell.msg_cells[msg_id] || null;
386 return codecell.CodeCell.msg_cells[msg_id] || null;
299 };
387 };
300
388
301 /**
389 /**
@@ -474,11 +562,11 b' var IPython = (function (IPython) {'
474 var cell = this.get_cell(index);
562 var cell = this.get_cell(index);
475 cell.select();
563 cell.select();
476 if (cell.cell_type === 'heading') {
564 if (cell.cell_type === 'heading') {
477 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
565 this.events.trigger('selected_cell_type_changed.Notebook',
478 {'cell_type':cell.cell_type,level:cell.level}
566 {'cell_type':cell.cell_type,level:cell.level}
479 );
567 );
480 } else {
568 } else {
481 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
569 this.events.trigger('selected_cell_type_changed.Notebook',
482 {'cell_type':cell.cell_type}
570 {'cell_type':cell.cell_type}
483 );
571 );
484 }
572 }
@@ -540,8 +628,8 b' var IPython = (function (IPython) {'
540 if (this.mode !== 'command') {
628 if (this.mode !== 'command') {
541 cell.command_mode();
629 cell.command_mode();
542 this.mode = 'command';
630 this.mode = 'command';
543 $([IPython.events]).trigger('command_mode.Notebook');
631 this.events.trigger('command_mode.Notebook');
544 IPython.keyboard_manager.command_mode();
632 this.keyboard_manager.command_mode();
545 }
633 }
546 };
634 };
547
635
@@ -570,8 +658,8 b' var IPython = (function (IPython) {'
570 if (cell && this.mode !== 'edit') {
658 if (cell && this.mode !== 'edit') {
571 cell.edit_mode();
659 cell.edit_mode();
572 this.mode = 'edit';
660 this.mode = 'edit';
573 $([IPython.events]).trigger('edit_mode.Notebook');
661 this.events.trigger('edit_mode.Notebook');
574 IPython.keyboard_manager.edit_mode();
662 this.keyboard_manager.edit_mode();
575 }
663 }
576 };
664 };
577
665
@@ -686,7 +774,7 b' var IPython = (function (IPython) {'
686 this.undelete_index = i;
774 this.undelete_index = i;
687 this.undelete_below = false;
775 this.undelete_below = false;
688 }
776 }
689 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
777 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
690 this.set_dirty(true);
778 this.set_dirty(true);
691 }
779 }
692 return this;
780 return this;
@@ -753,20 +841,27 b' var IPython = (function (IPython) {'
753 type = type || this.get_selected_cell().cell_type;
841 type = type || this.get_selected_cell().cell_type;
754
842
755 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
843 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
844 var cell_options = {
845 events: this.events,
846 config: this.config,
847 keyboard_manager: this.keyboard_manager,
848 notebook: this,
849 tooltip: this.tooltip,
850 };
756 if (type === 'code') {
851 if (type === 'code') {
757 cell = new IPython.CodeCell(this.kernel);
852 cell = new codecell.CodeCell(this.kernel, cell_options);
758 cell.set_input_prompt();
853 cell.set_input_prompt();
759 } else if (type === 'markdown') {
854 } else if (type === 'markdown') {
760 cell = new IPython.MarkdownCell();
855 cell = new textcell.MarkdownCell(cell_options);
761 } else if (type === 'raw') {
856 } else if (type === 'raw') {
762 cell = new IPython.RawCell();
857 cell = new textcell.RawCell(cell_options);
763 } else if (type === 'heading') {
858 } else if (type === 'heading') {
764 cell = new IPython.HeadingCell();
859 cell = new textcell.HeadingCell(cell_options);
765 }
860 }
766
861
767 if(this._insert_element_at_index(cell.element,index)) {
862 if(this._insert_element_at_index(cell.element,index)) {
768 cell.render();
863 cell.render();
769 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
864 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
770 cell.refresh();
865 cell.refresh();
771 // We used to select the cell after we refresh it, but there
866 // We used to select the cell after we refresh it, but there
772 // are now cases were this method is called where select is
867 // are now cases were this method is called where select is
@@ -876,7 +971,7 b' var IPython = (function (IPython) {'
876 if (this.is_valid_cell_index(i)) {
971 if (this.is_valid_cell_index(i)) {
877 var source_element = this.get_cell_element(i);
972 var source_element = this.get_cell_element(i);
878 var source_cell = source_element.data("cell");
973 var source_cell = source_element.data("cell");
879 if (!(source_cell instanceof IPython.CodeCell)) {
974 if (!(source_cell instanceof codecell.CodeCell)) {
880 var target_cell = this.insert_cell_below('code',i);
975 var target_cell = this.insert_cell_below('code',i);
881 var text = source_cell.get_text();
976 var text = source_cell.get_text();
882 if (text === source_cell.placeholder) {
977 if (text === source_cell.placeholder) {
@@ -906,7 +1001,7 b' var IPython = (function (IPython) {'
906 if (this.is_valid_cell_index(i)) {
1001 if (this.is_valid_cell_index(i)) {
907 var source_element = this.get_cell_element(i);
1002 var source_element = this.get_cell_element(i);
908 var source_cell = source_element.data("cell");
1003 var source_cell = source_element.data("cell");
909 if (!(source_cell instanceof IPython.MarkdownCell)) {
1004 if (!(source_cell instanceof textcell.MarkdownCell)) {
910 var target_cell = this.insert_cell_below('markdown',i);
1005 var target_cell = this.insert_cell_below('markdown',i);
911 var text = source_cell.get_text();
1006 var text = source_cell.get_text();
912 if (text === source_cell.placeholder) {
1007 if (text === source_cell.placeholder) {
@@ -920,7 +1015,7 b' var IPython = (function (IPython) {'
920 target_cell.code_mirror.clearHistory();
1015 target_cell.code_mirror.clearHistory();
921 source_element.remove();
1016 source_element.remove();
922 this.select(i);
1017 this.select(i);
923 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
1018 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
924 target_cell.render();
1019 target_cell.render();
925 }
1020 }
926 var cursor = source_cell.code_mirror.getCursor();
1021 var cursor = source_cell.code_mirror.getCursor();
@@ -942,7 +1037,7 b' var IPython = (function (IPython) {'
942 var source_element = this.get_cell_element(i);
1037 var source_element = this.get_cell_element(i);
943 var source_cell = source_element.data("cell");
1038 var source_cell = source_element.data("cell");
944 var target_cell = null;
1039 var target_cell = null;
945 if (!(source_cell instanceof IPython.RawCell)) {
1040 if (!(source_cell instanceof textcell.RawCell)) {
946 target_cell = this.insert_cell_below('raw',i);
1041 target_cell = this.insert_cell_below('raw',i);
947 var text = source_cell.get_text();
1042 var text = source_cell.get_text();
948 if (text === source_cell.placeholder) {
1043 if (text === source_cell.placeholder) {
@@ -977,7 +1072,7 b' var IPython = (function (IPython) {'
977 var source_element = this.get_cell_element(i);
1072 var source_element = this.get_cell_element(i);
978 var source_cell = source_element.data("cell");
1073 var source_cell = source_element.data("cell");
979 var target_cell = null;
1074 var target_cell = null;
980 if (source_cell instanceof IPython.HeadingCell) {
1075 if (source_cell instanceof textcell.HeadingCell) {
981 source_cell.set_level(level);
1076 source_cell.set_level(level);
982 } else {
1077 } else {
983 target_cell = this.insert_cell_below('heading',i);
1078 target_cell = this.insert_cell_below('heading',i);
@@ -996,12 +1091,12 b' var IPython = (function (IPython) {'
996 this.select(i);
1091 this.select(i);
997 var cursor = source_cell.code_mirror.getCursor();
1092 var cursor = source_cell.code_mirror.getCursor();
998 target_cell.code_mirror.setCursor(cursor);
1093 target_cell.code_mirror.setCursor(cursor);
999 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
1094 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1000 target_cell.render();
1095 target_cell.render();
1001 }
1096 }
1002 }
1097 }
1003 this.set_dirty(true);
1098 this.set_dirty(true);
1004 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1099 this.events.trigger('selected_cell_type_changed.Notebook',
1005 {'cell_type':'heading',level:level}
1100 {'cell_type':'heading',level:level}
1006 );
1101 );
1007 }
1102 }
@@ -1115,26 +1210,17 b' var IPython = (function (IPython) {'
1115 * @method split_cell
1210 * @method split_cell
1116 */
1211 */
1117 Notebook.prototype.split_cell = function () {
1212 Notebook.prototype.split_cell = function () {
1118 var mdc = IPython.MarkdownCell;
1213 var mdc = textcell.MarkdownCell;
1119 var rc = IPython.RawCell;
1214 var rc = textcell.RawCell;
1120 var cell = this.get_selected_cell();
1215 var cell = this.get_selected_cell();
1121 if (cell.is_splittable()) {
1216 if (cell.is_splittable()) {
1122 var texta = cell.get_pre_cursor();
1217 var texta = cell.get_pre_cursor();
1123 var textb = cell.get_post_cursor();
1218 var textb = cell.get_post_cursor();
1124 if (cell instanceof IPython.CodeCell) {
1219 cell.set_text(textb);
1125 // In this case the operations keep the notebook in its existing mode
1220 var new_cell = this.insert_cell_above(cell.cell_type);
1126 // so we don't need to do any post-op mode changes.
1221 // Unrender the new cell so we can call set_text.
1127 cell.set_text(textb);
1222 new_cell.unrender();
1128 var new_cell = this.insert_cell_above('code');
1223 new_cell.set_text(texta);
1129 new_cell.set_text(texta);
1130 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1131 // We know cell is !rendered so we can use set_text.
1132 cell.set_text(textb);
1133 var new_cell = this.insert_cell_above(cell.cell_type);
1134 // Unrender the new cell so we can call set_text.
1135 new_cell.unrender();
1136 new_cell.set_text(texta);
1137 }
1138 }
1224 }
1139 };
1225 };
1140
1226
@@ -1144,8 +1230,8 b' var IPython = (function (IPython) {'
1144 * @method merge_cell_above
1230 * @method merge_cell_above
1145 */
1231 */
1146 Notebook.prototype.merge_cell_above = function () {
1232 Notebook.prototype.merge_cell_above = function () {
1147 var mdc = IPython.MarkdownCell;
1233 var mdc = textcell.MarkdownCell;
1148 var rc = IPython.RawCell;
1234 var rc = textcell.RawCell;
1149 var index = this.get_selected_index();
1235 var index = this.get_selected_index();
1150 var cell = this.get_cell(index);
1236 var cell = this.get_cell(index);
1151 var render = cell.rendered;
1237 var render = cell.rendered;
@@ -1159,9 +1245,9 b' var IPython = (function (IPython) {'
1159 }
1245 }
1160 var upper_text = upper_cell.get_text();
1246 var upper_text = upper_cell.get_text();
1161 var text = cell.get_text();
1247 var text = cell.get_text();
1162 if (cell instanceof IPython.CodeCell) {
1248 if (cell instanceof codecell.CodeCell) {
1163 cell.set_text(upper_text+'\n'+text);
1249 cell.set_text(upper_text+'\n'+text);
1164 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1250 } else {
1165 cell.unrender(); // Must unrender before we set_text.
1251 cell.unrender(); // Must unrender before we set_text.
1166 cell.set_text(upper_text+'\n\n'+text);
1252 cell.set_text(upper_text+'\n\n'+text);
1167 if (render) {
1253 if (render) {
@@ -1181,8 +1267,8 b' var IPython = (function (IPython) {'
1181 * @method merge_cell_below
1267 * @method merge_cell_below
1182 */
1268 */
1183 Notebook.prototype.merge_cell_below = function () {
1269 Notebook.prototype.merge_cell_below = function () {
1184 var mdc = IPython.MarkdownCell;
1270 var mdc = textcell.MarkdownCell;
1185 var rc = IPython.RawCell;
1271 var rc = textcell.RawCell;
1186 var index = this.get_selected_index();
1272 var index = this.get_selected_index();
1187 var cell = this.get_cell(index);
1273 var cell = this.get_cell(index);
1188 var render = cell.rendered;
1274 var render = cell.rendered;
@@ -1196,9 +1282,9 b' var IPython = (function (IPython) {'
1196 }
1282 }
1197 var lower_text = lower_cell.get_text();
1283 var lower_text = lower_cell.get_text();
1198 var text = cell.get_text();
1284 var text = cell.get_text();
1199 if (cell instanceof IPython.CodeCell) {
1285 if (cell instanceof codecell.CodeCell) {
1200 cell.set_text(text+'\n'+lower_text);
1286 cell.set_text(text+'\n'+lower_text);
1201 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1287 } else {
1202 cell.unrender(); // Must unrender before we set_text.
1288 cell.unrender(); // Must unrender before we set_text.
1203 cell.set_text(text+'\n\n'+lower_text);
1289 cell.set_text(text+'\n\n'+lower_text);
1204 if (render) {
1290 if (render) {
@@ -1224,7 +1310,7 b' var IPython = (function (IPython) {'
1224 Notebook.prototype.collapse_output = function (index) {
1310 Notebook.prototype.collapse_output = function (index) {
1225 var i = this.index_or_selected(index);
1311 var i = this.index_or_selected(index);
1226 var cell = this.get_cell(i);
1312 var cell = this.get_cell(i);
1227 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1313 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1228 cell.collapse_output();
1314 cell.collapse_output();
1229 this.set_dirty(true);
1315 this.set_dirty(true);
1230 }
1316 }
@@ -1237,7 +1323,7 b' var IPython = (function (IPython) {'
1237 */
1323 */
1238 Notebook.prototype.collapse_all_output = function () {
1324 Notebook.prototype.collapse_all_output = function () {
1239 $.map(this.get_cells(), function (cell, i) {
1325 $.map(this.get_cells(), function (cell, i) {
1240 if (cell instanceof IPython.CodeCell) {
1326 if (cell instanceof codecell.CodeCell) {
1241 cell.collapse_output();
1327 cell.collapse_output();
1242 }
1328 }
1243 });
1329 });
@@ -1254,7 +1340,7 b' var IPython = (function (IPython) {'
1254 Notebook.prototype.expand_output = function (index) {
1340 Notebook.prototype.expand_output = function (index) {
1255 var i = this.index_or_selected(index);
1341 var i = this.index_or_selected(index);
1256 var cell = this.get_cell(i);
1342 var cell = this.get_cell(i);
1257 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1343 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1258 cell.expand_output();
1344 cell.expand_output();
1259 this.set_dirty(true);
1345 this.set_dirty(true);
1260 }
1346 }
@@ -1267,7 +1353,7 b' var IPython = (function (IPython) {'
1267 */
1353 */
1268 Notebook.prototype.expand_all_output = function () {
1354 Notebook.prototype.expand_all_output = function () {
1269 $.map(this.get_cells(), function (cell, i) {
1355 $.map(this.get_cells(), function (cell, i) {
1270 if (cell instanceof IPython.CodeCell) {
1356 if (cell instanceof codecell.CodeCell) {
1271 cell.expand_output();
1357 cell.expand_output();
1272 }
1358 }
1273 });
1359 });
@@ -1284,7 +1370,7 b' var IPython = (function (IPython) {'
1284 Notebook.prototype.clear_output = function (index) {
1370 Notebook.prototype.clear_output = function (index) {
1285 var i = this.index_or_selected(index);
1371 var i = this.index_or_selected(index);
1286 var cell = this.get_cell(i);
1372 var cell = this.get_cell(i);
1287 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1373 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1288 cell.clear_output();
1374 cell.clear_output();
1289 this.set_dirty(true);
1375 this.set_dirty(true);
1290 }
1376 }
@@ -1297,7 +1383,7 b' var IPython = (function (IPython) {'
1297 */
1383 */
1298 Notebook.prototype.clear_all_output = function () {
1384 Notebook.prototype.clear_all_output = function () {
1299 $.map(this.get_cells(), function (cell, i) {
1385 $.map(this.get_cells(), function (cell, i) {
1300 if (cell instanceof IPython.CodeCell) {
1386 if (cell instanceof codecell.CodeCell) {
1301 cell.clear_output();
1387 cell.clear_output();
1302 }
1388 }
1303 });
1389 });
@@ -1313,7 +1399,7 b' var IPython = (function (IPython) {'
1313 Notebook.prototype.scroll_output = function (index) {
1399 Notebook.prototype.scroll_output = function (index) {
1314 var i = this.index_or_selected(index);
1400 var i = this.index_or_selected(index);
1315 var cell = this.get_cell(i);
1401 var cell = this.get_cell(i);
1316 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1402 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1317 cell.scroll_output();
1403 cell.scroll_output();
1318 this.set_dirty(true);
1404 this.set_dirty(true);
1319 }
1405 }
@@ -1326,7 +1412,7 b' var IPython = (function (IPython) {'
1326 */
1412 */
1327 Notebook.prototype.scroll_all_output = function () {
1413 Notebook.prototype.scroll_all_output = function () {
1328 $.map(this.get_cells(), function (cell, i) {
1414 $.map(this.get_cells(), function (cell, i) {
1329 if (cell instanceof IPython.CodeCell) {
1415 if (cell instanceof codecell.CodeCell) {
1330 cell.scroll_output();
1416 cell.scroll_output();
1331 }
1417 }
1332 });
1418 });
@@ -1342,7 +1428,7 b' var IPython = (function (IPython) {'
1342 Notebook.prototype.toggle_output = function (index) {
1428 Notebook.prototype.toggle_output = function (index) {
1343 var i = this.index_or_selected(index);
1429 var i = this.index_or_selected(index);
1344 var cell = this.get_cell(i);
1430 var cell = this.get_cell(i);
1345 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1431 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1346 cell.toggle_output();
1432 cell.toggle_output();
1347 this.set_dirty(true);
1433 this.set_dirty(true);
1348 }
1434 }
@@ -1355,7 +1441,7 b' var IPython = (function (IPython) {'
1355 */
1441 */
1356 Notebook.prototype.toggle_all_output = function () {
1442 Notebook.prototype.toggle_all_output = function () {
1357 $.map(this.get_cells(), function (cell, i) {
1443 $.map(this.get_cells(), function (cell, i) {
1358 if (cell instanceof IPython.CodeCell) {
1444 if (cell instanceof codecell.CodeCell) {
1359 cell.toggle_output();
1445 cell.toggle_output();
1360 }
1446 }
1361 });
1447 });
@@ -1372,7 +1458,7 b' var IPython = (function (IPython) {'
1372 Notebook.prototype.toggle_output_scroll = function (index) {
1458 Notebook.prototype.toggle_output_scroll = function (index) {
1373 var i = this.index_or_selected(index);
1459 var i = this.index_or_selected(index);
1374 var cell = this.get_cell(i);
1460 var cell = this.get_cell(i);
1375 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1461 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1376 cell.toggle_output_scroll();
1462 cell.toggle_output_scroll();
1377 this.set_dirty(true);
1463 this.set_dirty(true);
1378 }
1464 }
@@ -1385,7 +1471,7 b' var IPython = (function (IPython) {'
1385 */
1471 */
1386 Notebook.prototype.toggle_all_output_scroll = function () {
1472 Notebook.prototype.toggle_all_output_scroll = function () {
1387 $.map(this.get_cells(), function (cell, i) {
1473 $.map(this.get_cells(), function (cell, i) {
1388 if (cell instanceof IPython.CodeCell) {
1474 if (cell instanceof codecell.CodeCell) {
1389 cell.toggle_output_scroll();
1475 cell.toggle_output_scroll();
1390 }
1476 }
1391 });
1477 });
@@ -1403,6 +1489,34 b' var IPython = (function (IPython) {'
1403 Notebook.prototype.cell_toggle_line_numbers = function() {
1489 Notebook.prototype.cell_toggle_line_numbers = function() {
1404 this.get_selected_cell().toggle_line_numbers();
1490 this.get_selected_cell().toggle_line_numbers();
1405 };
1491 };
1492
1493 /**
1494 * Set the codemirror mode for all code cells, including the default for
1495 * new code cells.
1496 *
1497 * @method set_codemirror_mode
1498 */
1499 Notebook.prototype.set_codemirror_mode = function(newmode){
1500 if (newmode === this.codemirror_mode) {
1501 return;
1502 }
1503 this.codemirror_mode = newmode;
1504 codecell.CodeCell.options_default.cm_config.mode = newmode;
1505 modename = newmode.name || newmode
1506
1507 that = this;
1508 CodeMirror.requireMode(modename, function(){
1509 $.map(that.get_cells(), function(cell, i) {
1510 if (cell.cell_type === 'code'){
1511 cell.code_mirror.setOption('mode', newmode);
1512 // This is currently redundant, because cm_config ends up as
1513 // codemirror's own .options object, but I don't want to
1514 // rely on that.
1515 cell.cm_config.mode = newmode;
1516 }
1517 });
1518 })
1519 };
1406
1520
1407 // Session related things
1521 // Session related things
1408
1522
@@ -1411,9 +1525,54 b' var IPython = (function (IPython) {'
1411 *
1525 *
1412 * @method start_session
1526 * @method start_session
1413 */
1527 */
1414 Notebook.prototype.start_session = function () {
1528 Notebook.prototype.start_session = function (kernel_name) {
1415 this.session = new IPython.Session(this, this.options);
1529 var that = this;
1416 this.session.start($.proxy(this._session_started, this));
1530 if (kernel_name === undefined) {
1531 kernel_name = this.default_kernel_name;
1532 }
1533 if (this._session_starting) {
1534 throw new session.SessionAlreadyStarting();
1535 }
1536 this._session_starting = true;
1537
1538 if (this.session !== null) {
1539 var s = this.session;
1540 this.session = null;
1541 // need to start the new session in a callback after delete,
1542 // because javascript does not guarantee the ordering of AJAX requests (?!)
1543 s.delete(function () {
1544 // on successful delete, start new session
1545 that._session_starting = false;
1546 that.start_session(kernel_name);
1547 }, function (jqXHR, status, error) {
1548 // log the failed delete, but still create a new session
1549 // 404 just means it was already deleted by someone else,
1550 // but other errors are possible.
1551 utils.log_ajax_error(jqXHR, status, error);
1552 that._session_starting = false;
1553 that.start_session(kernel_name);
1554 }
1555 );
1556 return;
1557 }
1558
1559
1560
1561 this.session = new session.Session({
1562 base_url: this.base_url,
1563 ws_url: this.ws_url,
1564 notebook_path: this.notebook_path,
1565 notebook_name: this.notebook_name,
1566 // For now, create all sessions with the 'python' kernel, which is the
1567 // default. Later, the user will be able to select kernels. This is
1568 // overridden if KernelManager.kernel_cmd is specified for the server.
1569 kernel_name: kernel_name,
1570 notebook: this});
1571
1572 this.session.start(
1573 $.proxy(this._session_started, this),
1574 $.proxy(this._session_start_failed, this)
1575 );
1417 };
1576 };
1418
1577
1419
1578
@@ -1422,17 +1581,22 b' var IPython = (function (IPython) {'
1422 * comm manager to the widget manager
1581 * comm manager to the widget manager
1423 *
1582 *
1424 */
1583 */
1425 Notebook.prototype._session_started = function(){
1584 Notebook.prototype._session_started = function (){
1585 this._session_starting = false;
1426 this.kernel = this.session.kernel;
1586 this.kernel = this.session.kernel;
1427 var ncells = this.ncells();
1587 var ncells = this.ncells();
1428 for (var i=0; i<ncells; i++) {
1588 for (var i=0; i<ncells; i++) {
1429 var cell = this.get_cell(i);
1589 var cell = this.get_cell(i);
1430 if (cell instanceof IPython.CodeCell) {
1590 if (cell instanceof codecell.CodeCell) {
1431 cell.set_kernel(this.session.kernel);
1591 cell.set_kernel(this.session.kernel);
1432 }
1592 }
1433 }
1593 }
1434 };
1594 };
1435
1595 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1596 this._session_starting = false;
1597 utils.log_ajax_error(jqxhr, status, error);
1598 };
1599
1436 /**
1600 /**
1437 * Prompt the user to restart the IPython kernel.
1601 * Prompt the user to restart the IPython kernel.
1438 *
1602 *
@@ -1440,7 +1604,9 b' var IPython = (function (IPython) {'
1440 */
1604 */
1441 Notebook.prototype.restart_kernel = function () {
1605 Notebook.prototype.restart_kernel = function () {
1442 var that = this;
1606 var that = this;
1443 IPython.dialog.modal({
1607 dialog.modal({
1608 notebook: this,
1609 keyboard_manager: this.keyboard_manager,
1444 title : "Restart kernel or continue running?",
1610 title : "Restart kernel or continue running?",
1445 body : $("<p/>").text(
1611 body : $("<p/>").text(
1446 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1612 'Do you want to restart the current kernel? You will lose all variables defined in it.'
@@ -1633,6 +1799,13 b' var IPython = (function (IPython) {'
1633 this.metadata = content.metadata;
1799 this.metadata = content.metadata;
1634 this.notebook_name = data.name;
1800 this.notebook_name = data.name;
1635 var trusted = true;
1801 var trusted = true;
1802
1803 // Trigger an event changing the kernel spec - this will set the default
1804 // codemirror mode
1805 if (this.metadata.kernelspec !== undefined) {
1806 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1807 }
1808
1636 // Only handle 1 worksheet for now.
1809 // Only handle 1 worksheet for now.
1637 var worksheet = content.worksheets[0];
1810 var worksheet = content.worksheets[0];
1638 if (worksheet !== undefined) {
1811 if (worksheet !== undefined) {
@@ -1660,10 +1833,12 b' var IPython = (function (IPython) {'
1660 }
1833 }
1661 if (trusted != this.trusted) {
1834 if (trusted != this.trusted) {
1662 this.trusted = trusted;
1835 this.trusted = trusted;
1663 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1836 this.events.trigger("trust_changed.Notebook", trusted);
1664 }
1837 }
1665 if (content.worksheets.length > 1) {
1838 if (content.worksheets.length > 1) {
1666 IPython.dialog.modal({
1839 dialog.modal({
1840 notebook: this,
1841 keyboard_manager: this.keyboard_manager,
1667 title : "Multiple worksheets",
1842 title : "Multiple worksheets",
1668 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1843 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1669 "but this version of IPython can only handle the first. " +
1844 "but this version of IPython can only handle the first. " +
@@ -1705,7 +1880,7 b' var IPython = (function (IPython) {'
1705 };
1880 };
1706 if (trusted != this.trusted) {
1881 if (trusted != this.trusted) {
1707 this.trusted = trusted;
1882 this.trusted = trusted;
1708 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1883 this.events.trigger("trust_changed.Notebook", trusted);
1709 }
1884 }
1710 return data;
1885 return data;
1711 };
1886 };
@@ -1730,10 +1905,10 b' var IPython = (function (IPython) {'
1730 that.save_notebook();
1905 that.save_notebook();
1731 }
1906 }
1732 }, interval);
1907 }, interval);
1733 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1908 this.events.trigger("autosave_enabled.Notebook", interval);
1734 } else {
1909 } else {
1735 this.autosave_timer = null;
1910 this.autosave_timer = null;
1736 $([IPython.events]).trigger("autosave_disabled.Notebook");
1911 this.events.trigger("autosave_disabled.Notebook");
1737 }
1912 }
1738 };
1913 };
1739
1914
@@ -1748,6 +1923,8 b' var IPython = (function (IPython) {'
1748 var model = {};
1923 var model = {};
1749 model.name = this.notebook_name;
1924 model.name = this.notebook_name;
1750 model.path = this.notebook_path;
1925 model.path = this.notebook_path;
1926 model.type = 'notebook';
1927 model.format = 'json';
1751 model.content = this.toJSON();
1928 model.content = this.toJSON();
1752 model.content.nbformat = this.nbformat;
1929 model.content.nbformat = this.nbformat;
1753 model.content.nbformat_minor = this.nbformat_minor;
1930 model.content.nbformat_minor = this.nbformat_minor;
@@ -1768,10 +1945,10 b' var IPython = (function (IPython) {'
1768 settings[key] = extra_settings[key];
1945 settings[key] = extra_settings[key];
1769 }
1946 }
1770 }
1947 }
1771 $([IPython.events]).trigger('notebook_saving.Notebook');
1948 this.events.trigger('notebook_saving.Notebook');
1772 var url = utils.url_join_encode(
1949 var url = utils.url_join_encode(
1773 this.base_url,
1950 this.base_url,
1774 'api/notebooks',
1951 'api/contents',
1775 this.notebook_path,
1952 this.notebook_path,
1776 this.notebook_name
1953 this.notebook_name
1777 );
1954 );
@@ -1789,7 +1966,7 b' var IPython = (function (IPython) {'
1789 */
1966 */
1790 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1967 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1791 this.set_dirty(false);
1968 this.set_dirty(false);
1792 $([IPython.events]).trigger('notebook_saved.Notebook');
1969 this.events.trigger('notebook_saved.Notebook');
1793 this._update_autosave_interval(start);
1970 this._update_autosave_interval(start);
1794 if (this._checkpoint_after_save) {
1971 if (this._checkpoint_after_save) {
1795 this.create_checkpoint();
1972 this.create_checkpoint();
@@ -1826,7 +2003,7 b' var IPython = (function (IPython) {'
1826 * @param {String} error HTTP error message
2003 * @param {String} error HTTP error message
1827 */
2004 */
1828 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
2005 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1829 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
2006 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1830 };
2007 };
1831
2008
1832 /**
2009 /**
@@ -1851,7 +2028,9 b' var IPython = (function (IPython) {'
1851 );
2028 );
1852
2029
1853 var nb = this;
2030 var nb = this;
1854 IPython.dialog.modal({
2031 dialog.modal({
2032 notebook: this,
2033 keyboard_manager: this.keyboard_manager,
1855 title: "Trust this notebook?",
2034 title: "Trust this notebook?",
1856 body: body,
2035 body: body,
1857
2036
@@ -1867,7 +2046,7 b' var IPython = (function (IPython) {'
1867 cell.output_area.trusted = true;
2046 cell.output_area.trusted = true;
1868 }
2047 }
1869 }
2048 }
1870 $([IPython.events]).on('notebook_saved.Notebook', function () {
2049 this.events.on('notebook_saved.Notebook', function () {
1871 window.location.reload();
2050 window.location.reload();
1872 });
2051 });
1873 nb.save_notebook();
2052 nb.save_notebook();
@@ -1902,7 +2081,7 b' var IPython = (function (IPython) {'
1902 };
2081 };
1903 var url = utils.url_join_encode(
2082 var url = utils.url_join_encode(
1904 base_url,
2083 base_url,
1905 'api/notebooks',
2084 'api/contents',
1906 path
2085 path
1907 );
2086 );
1908 $.ajax(url,settings);
2087 $.ajax(url,settings);
@@ -1931,7 +2110,7 b' var IPython = (function (IPython) {'
1931 };
2110 };
1932 var url = utils.url_join_encode(
2111 var url = utils.url_join_encode(
1933 base_url,
2112 base_url,
1934 'api/notebooks',
2113 'api/contents',
1935 path
2114 path
1936 );
2115 );
1937 $.ajax(url,settings);
2116 $.ajax(url,settings);
@@ -1953,10 +2132,10 b' var IPython = (function (IPython) {'
1953 success : $.proxy(that.rename_success, this),
2132 success : $.proxy(that.rename_success, this),
1954 error : $.proxy(that.rename_error, this)
2133 error : $.proxy(that.rename_error, this)
1955 };
2134 };
1956 $([IPython.events]).trigger('rename_notebook.Notebook', data);
2135 this.events.trigger('rename_notebook.Notebook', data);
1957 var url = utils.url_join_encode(
2136 var url = utils.url_join_encode(
1958 this.base_url,
2137 this.base_url,
1959 'api/notebooks',
2138 'api/contents',
1960 this.notebook_path,
2139 this.notebook_path,
1961 this.notebook_name
2140 this.notebook_name
1962 );
2141 );
@@ -1974,7 +2153,7 b' var IPython = (function (IPython) {'
1974 };
2153 };
1975 var url = utils.url_join_encode(
2154 var url = utils.url_join_encode(
1976 this.base_url,
2155 this.base_url,
1977 'api/notebooks',
2156 'api/contents',
1978 this.notebook_path,
2157 this.notebook_path,
1979 this.notebook_name
2158 this.notebook_name
1980 );
2159 );
@@ -1986,32 +2165,33 b' var IPython = (function (IPython) {'
1986 var name = this.notebook_name = json.name;
2165 var name = this.notebook_name = json.name;
1987 var path = json.path;
2166 var path = json.path;
1988 this.session.rename_notebook(name, path);
2167 this.session.rename_notebook(name, path);
1989 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
2168 this.events.trigger('notebook_renamed.Notebook', json);
1990 };
2169 };
1991
2170
1992 Notebook.prototype.rename_error = function (xhr, status, error) {
2171 Notebook.prototype.rename_error = function (xhr, status, error) {
1993 var that = this;
2172 var that = this;
1994 var dialog = $('<div/>').append(
2173 var dialog_body = $('<div/>').append(
1995 $("<p/>").addClass("rename-message")
2174 $("<p/>").text('This notebook name already exists.')
1996 .text('This notebook name already exists.')
1997 );
2175 );
1998 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2176 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1999 IPython.dialog.modal({
2177 dialog.modal({
2178 notebook: this,
2179 keyboard_manager: this.keyboard_manager,
2000 title: "Notebook Rename Error!",
2180 title: "Notebook Rename Error!",
2001 body: dialog,
2181 body: dialog_body,
2002 buttons : {
2182 buttons : {
2003 "Cancel": {},
2183 "Cancel": {},
2004 "OK": {
2184 "OK": {
2005 class: "btn-primary",
2185 class: "btn-primary",
2006 click: function () {
2186 click: function () {
2007 IPython.save_widget.rename_notebook();
2187 this.save_widget.rename_notebook({notebook:that});
2008 }}
2188 }}
2009 },
2189 },
2010 open : function (event, ui) {
2190 open : function (event, ui) {
2011 var that = $(this);
2191 var that = $(this);
2012 // Upon ENTER, click the OK button.
2192 // Upon ENTER, click the OK button.
2013 that.find('input[type="text"]').keydown(function (event, ui) {
2193 that.find('input[type="text"]').keydown(function (event, ui) {
2014 if (event.which === IPython.keyboard.keycodes.enter) {
2194 if (event.which === this.keyboard.keycodes.enter) {
2015 that.find('.btn-primary').first().click();
2195 that.find('.btn-primary').first().click();
2016 }
2196 }
2017 });
2197 });
@@ -2039,10 +2219,10 b' var IPython = (function (IPython) {'
2039 success : $.proxy(this.load_notebook_success,this),
2219 success : $.proxy(this.load_notebook_success,this),
2040 error : $.proxy(this.load_notebook_error,this),
2220 error : $.proxy(this.load_notebook_error,this),
2041 };
2221 };
2042 $([IPython.events]).trigger('notebook_loading.Notebook');
2222 this.events.trigger('notebook_loading.Notebook');
2043 var url = utils.url_join_encode(
2223 var url = utils.url_join_encode(
2044 this.base_url,
2224 this.base_url,
2045 'api/notebooks',
2225 'api/contents',
2046 this.notebook_path,
2226 this.notebook_path,
2047 this.notebook_name
2227 this.notebook_name
2048 );
2228 );
@@ -2077,7 +2257,9 b' var IPython = (function (IPython) {'
2077 "newer notebook format will be used and older versions of IPython " +
2257 "newer notebook format will be used and older versions of IPython " +
2078 "may not be able to read it. To keep the older version, close the " +
2258 "may not be able to read it. To keep the older version, close the " +
2079 "notebook without saving it.";
2259 "notebook without saving it.";
2080 IPython.dialog.modal({
2260 dialog.modal({
2261 notebook: this,
2262 keyboard_manager: this.keyboard_manager,
2081 title : "Notebook converted",
2263 title : "Notebook converted",
2082 body : msg,
2264 body : msg,
2083 buttons : {
2265 buttons : {
@@ -2094,7 +2276,9 b' var IPython = (function (IPython) {'
2094 this_vs + ". You can still work with this notebook, but some features " +
2276 this_vs + ". You can still work with this notebook, but some features " +
2095 "introduced in later notebook versions may not be available.";
2277 "introduced in later notebook versions may not be available.";
2096
2278
2097 IPython.dialog.modal({
2279 dialog.modal({
2280 notebook: this,
2281 keyboard_manager: this.keyboard_manager,
2098 title : "Newer Notebook",
2282 title : "Newer Notebook",
2099 body : msg,
2283 body : msg,
2100 buttons : {
2284 buttons : {
@@ -2109,22 +2293,25 b' var IPython = (function (IPython) {'
2109 // Create the session after the notebook is completely loaded to prevent
2293 // Create the session after the notebook is completely loaded to prevent
2110 // code execution upon loading, which is a security risk.
2294 // code execution upon loading, which is a security risk.
2111 if (this.session === null) {
2295 if (this.session === null) {
2112 this.start_session();
2296 var kernelspec = this.metadata.kernelspec || {};
2297 var kernel_name = kernelspec.name || this.default_kernel_name;
2298
2299 this.start_session(kernel_name);
2113 }
2300 }
2114 // load our checkpoint list
2301 // load our checkpoint list
2115 this.list_checkpoints();
2302 this.list_checkpoints();
2116
2303
2117 // load toolbar state
2304 // load toolbar state
2118 if (this.metadata.celltoolbar) {
2305 if (this.metadata.celltoolbar) {
2119 IPython.CellToolbar.global_show();
2306 celltoolbar.CellToolbar.global_show();
2120 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2307 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2121 } else {
2308 } else {
2122 IPython.CellToolbar.global_hide();
2309 celltoolbar.CellToolbar.global_hide();
2123 }
2310 }
2124
2311
2125 // now that we're fully loaded, it is safe to restore save functionality
2312 // now that we're fully loaded, it is safe to restore save functionality
2126 delete(this.save_notebook);
2313 delete(this.save_notebook);
2127 $([IPython.events]).trigger('notebook_loaded.Notebook');
2314 this.events.trigger('notebook_loaded.Notebook');
2128 };
2315 };
2129
2316
2130 /**
2317 /**
@@ -2136,16 +2323,19 b' var IPython = (function (IPython) {'
2136 * @param {String} error HTTP error message
2323 * @param {String} error HTTP error message
2137 */
2324 */
2138 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2325 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2139 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2326 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2327 utils.log_ajax_error(xhr, status, error);
2140 var msg;
2328 var msg;
2141 if (xhr.status === 400) {
2329 if (xhr.status === 400) {
2142 msg = error;
2330 msg = escape(utils.ajax_error_msg(xhr));
2143 } else if (xhr.status === 500) {
2331 } else if (xhr.status === 500) {
2144 msg = "An unknown error occurred while loading this notebook. " +
2332 msg = "An unknown error occurred while loading this notebook. " +
2145 "This version can load notebook formats " +
2333 "This version can load notebook formats " +
2146 "v" + this.nbformat + " or earlier.";
2334 "v" + this.nbformat + " or earlier. See the server log for details.";
2147 }
2335 }
2148 IPython.dialog.modal({
2336 dialog.modal({
2337 notebook: this,
2338 keyboard_manager: this.keyboard_manager,
2149 title: "Error loading notebook",
2339 title: "Error loading notebook",
2150 body : msg,
2340 body : msg,
2151 buttons : {
2341 buttons : {
@@ -2196,7 +2386,7 b' var IPython = (function (IPython) {'
2196 Notebook.prototype.list_checkpoints = function () {
2386 Notebook.prototype.list_checkpoints = function () {
2197 var url = utils.url_join_encode(
2387 var url = utils.url_join_encode(
2198 this.base_url,
2388 this.base_url,
2199 'api/notebooks',
2389 'api/contents',
2200 this.notebook_path,
2390 this.notebook_path,
2201 this.notebook_name,
2391 this.notebook_name,
2202 'checkpoints'
2392 'checkpoints'
@@ -2224,7 +2414,7 b' var IPython = (function (IPython) {'
2224 } else {
2414 } else {
2225 this.last_checkpoint = null;
2415 this.last_checkpoint = null;
2226 }
2416 }
2227 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2417 this.events.trigger('checkpoints_listed.Notebook', [data]);
2228 };
2418 };
2229
2419
2230 /**
2420 /**
@@ -2236,7 +2426,7 b' var IPython = (function (IPython) {'
2236 * @param {String} error_msg HTTP error message
2426 * @param {String} error_msg HTTP error message
2237 */
2427 */
2238 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2428 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2239 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2429 this.events.trigger('list_checkpoints_failed.Notebook');
2240 };
2430 };
2241
2431
2242 /**
2432 /**
@@ -2247,7 +2437,7 b' var IPython = (function (IPython) {'
2247 Notebook.prototype.create_checkpoint = function () {
2437 Notebook.prototype.create_checkpoint = function () {
2248 var url = utils.url_join_encode(
2438 var url = utils.url_join_encode(
2249 this.base_url,
2439 this.base_url,
2250 'api/notebooks',
2440 'api/contents',
2251 this.notebook_path,
2441 this.notebook_path,
2252 this.notebook_name,
2442 this.notebook_name,
2253 'checkpoints'
2443 'checkpoints'
@@ -2270,7 +2460,7 b' var IPython = (function (IPython) {'
2270 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2460 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2271 data = $.parseJSON(data);
2461 data = $.parseJSON(data);
2272 this.add_checkpoint(data);
2462 this.add_checkpoint(data);
2273 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2463 this.events.trigger('checkpoint_created.Notebook', data);
2274 };
2464 };
2275
2465
2276 /**
2466 /**
@@ -2282,7 +2472,7 b' var IPython = (function (IPython) {'
2282 * @param {String} error_msg HTTP error message
2472 * @param {String} error_msg HTTP error message
2283 */
2473 */
2284 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2474 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2285 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2475 this.events.trigger('checkpoint_failed.Notebook');
2286 };
2476 };
2287
2477
2288 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2478 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
@@ -2309,7 +2499,9 b' var IPython = (function (IPython) {'
2309 ).css("text-align", "center")
2499 ).css("text-align", "center")
2310 );
2500 );
2311
2501
2312 IPython.dialog.modal({
2502 dialog.modal({
2503 notebook: this,
2504 keyboard_manager: this.keyboard_manager,
2313 title : "Revert notebook to checkpoint",
2505 title : "Revert notebook to checkpoint",
2314 body : body,
2506 body : body,
2315 buttons : {
2507 buttons : {
@@ -2331,10 +2523,10 b' var IPython = (function (IPython) {'
2331 * @param {String} checkpoint ID
2523 * @param {String} checkpoint ID
2332 */
2524 */
2333 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2525 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2334 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2526 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2335 var url = utils.url_join_encode(
2527 var url = utils.url_join_encode(
2336 this.base_url,
2528 this.base_url,
2337 'api/notebooks',
2529 'api/contents',
2338 this.notebook_path,
2530 this.notebook_path,
2339 this.notebook_name,
2531 this.notebook_name,
2340 'checkpoints',
2532 'checkpoints',
@@ -2356,7 +2548,7 b' var IPython = (function (IPython) {'
2356 * @param {jqXHR} xhr jQuery Ajax object
2548 * @param {jqXHR} xhr jQuery Ajax object
2357 */
2549 */
2358 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2550 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2359 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2551 this.events.trigger('checkpoint_restored.Notebook');
2360 this.load_notebook(this.notebook_name, this.notebook_path);
2552 this.load_notebook(this.notebook_name, this.notebook_path);
2361 };
2553 };
2362
2554
@@ -2369,7 +2561,7 b' var IPython = (function (IPython) {'
2369 * @param {String} error_msg HTTP error message
2561 * @param {String} error_msg HTTP error message
2370 */
2562 */
2371 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2563 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2372 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2564 this.events.trigger('checkpoint_restore_failed.Notebook');
2373 };
2565 };
2374
2566
2375 /**
2567 /**
@@ -2379,10 +2571,10 b' var IPython = (function (IPython) {'
2379 * @param {String} checkpoint ID
2571 * @param {String} checkpoint ID
2380 */
2572 */
2381 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2573 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2382 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2574 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2383 var url = utils.url_join_encode(
2575 var url = utils.url_join_encode(
2384 this.base_url,
2576 this.base_url,
2385 'api/notebooks',
2577 'api/contents',
2386 this.notebook_path,
2578 this.notebook_path,
2387 this.notebook_name,
2579 this.notebook_name,
2388 'checkpoints',
2580 'checkpoints',
@@ -2404,7 +2596,7 b' var IPython = (function (IPython) {'
2404 * @param {jqXHR} xhr jQuery Ajax object
2596 * @param {jqXHR} xhr jQuery Ajax object
2405 */
2597 */
2406 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2598 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2407 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2599 this.events.trigger('checkpoint_deleted.Notebook', data);
2408 this.load_notebook(this.notebook_name, this.notebook_path);
2600 this.load_notebook(this.notebook_name, this.notebook_path);
2409 };
2601 };
2410
2602
@@ -2414,17 +2606,15 b' var IPython = (function (IPython) {'
2414 * @method delete_checkpoint_error
2606 * @method delete_checkpoint_error
2415 * @param {jqXHR} xhr jQuery Ajax object
2607 * @param {jqXHR} xhr jQuery Ajax object
2416 * @param {String} status Description of response status
2608 * @param {String} status Description of response status
2417 * @param {String} error_msg HTTP error message
2609 * @param {String} error HTTP error message
2418 */
2610 */
2419 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2611 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
2420 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2612 this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
2421 };
2613 };
2422
2614
2423
2615
2616 // For backwards compatability.
2424 IPython.Notebook = Notebook;
2617 IPython.Notebook = Notebook;
2425
2618
2426
2619 return {'Notebook': Notebook};
2427 return IPython;
2620 });
2428
2429 }(IPython));
2430
@@ -1,21 +1,31 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Notification widget
10 //============================================================================
11
12 var IPython = (function (IPython) {
13 "use strict";
14 var utils = IPython.utils;
15
3
4 define([
5 'base/js/namespace',
6 'jquery',
7 'base/js/utils',
8 'base/js/dialog',
9 'notebook/js/notificationwidget',
10 'moment'
11 ], function(IPython, $, utils, dialog, notificationwidget, moment) {
12 "use strict";
16
13
17 var NotificationArea = function (selector) {
14 var NotificationArea = function (selector, options) {
15 // Constructor
16 //
17 // Parameters:
18 // selector: string
19 // options: dictionary
20 // Dictionary of keyword arguments.
21 // notebook: Notebook instance
22 // events: $(Events) instance
23 // save_widget: SaveWidget instance
18 this.selector = selector;
24 this.selector = selector;
25 this.events = options.events;
26 this.save_widget = options.save_widget;
27 this.notebook = options.notebook;
28 this.keyboard_manager = options.keyboard_manager;
19 if (this.selector !== undefined) {
29 if (this.selector !== undefined) {
20 this.element = $(selector);
30 this.element = $(selector);
21 }
31 }
@@ -23,13 +33,10 b' var IPython = (function (IPython) {'
23 };
33 };
24
34
25 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
35 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
26 var uuid = utils.uuid();
27 if( css_class == 'danger') {css_class = 'ui-state-error';}
36 if( css_class == 'danger') {css_class = 'ui-state-error';}
28 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
37 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
29 var tdiv = $('<div>')
38 var tdiv = $('<div>')
30 .attr('id',uuid)
39 .addClass('notification_widget')
31 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
32 .addClass('border-box-sizing')
33 .addClass(css_class)
40 .addClass(css_class)
34 .hide()
41 .hide()
35 .text(msg);
42 .text(msg);
@@ -63,46 +70,53 b' var IPython = (function (IPython) {'
63 }
70 }
64 var div = $('<div/>').attr('id','notification_'+name);
71 var div = $('<div/>').attr('id','notification_'+name);
65 $(this.selector).append(div);
72 $(this.selector).append(div);
66 this.widget_dict[name] = new IPython.NotificationWidget('#notification_'+name);
73 this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name);
67 return this.widget_dict[name];
74 return this.widget_dict[name];
68 };
75 };
69
76
70 NotificationArea.prototype.init_notification_widgets = function() {
77 NotificationArea.prototype.init_notification_widgets = function() {
78 var that = this;
71 var knw = this.new_notification_widget('kernel');
79 var knw = this.new_notification_widget('kernel');
72 var $kernel_ind_icon = $("#kernel_indicator_icon");
80 var $kernel_ind_icon = $("#kernel_indicator_icon");
73 var $modal_ind_icon = $("#modal_indicator_icon");
81 var $modal_ind_icon = $("#modal_indicator_icon");
74
82
75 // Command/Edit mode
83 // Command/Edit mode
76 $([IPython.events]).on('edit_mode.Notebook',function () {
84 this.events.on('edit_mode.Notebook',function () {
77 IPython.save_widget.update_document_title();
85 that.save_widget.update_document_title();
78 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
86 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
79 });
87 });
80
88
81 $([IPython.events]).on('command_mode.Notebook',function () {
89 this.events.on('command_mode.Notebook',function () {
82 IPython.save_widget.update_document_title();
90 that.save_widget.update_document_title();
83 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
91 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
84 });
92 });
85
93
86 // Implicitly start off in Command mode, switching to Edit mode will trigger event
94 // Implicitly start off in Command mode, switching to Edit mode will trigger event
87 $modal_ind_icon.attr('class','command-mode_icon').attr('title','Command Mode');
95 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
88
96
89 // Kernel events
97 // Kernel events
90 $([IPython.events]).on('status_idle.Kernel',function () {
98 this.events.on('status_idle.Kernel',function () {
91 IPython.save_widget.update_document_title();
99 that.save_widget.update_document_title();
92 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
100 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
93 });
101 });
94
102
95 $([IPython.events]).on('status_busy.Kernel',function () {
103 this.events.on('status_busy.Kernel',function () {
96 window.document.title='(Busy) '+window.document.title;
104 window.document.title='(Busy) '+window.document.title;
97 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
105 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
98 });
106 });
99
107
100 $([IPython.events]).on('status_restarting.Kernel',function () {
108 this.events.on('status_restarting.Kernel',function () {
101 IPython.save_widget.update_document_title();
109 that.save_widget.update_document_title();
102 knw.set_message("Restarting kernel", 2000);
110 knw.set_message("Restarting kernel", 2000);
103 });
111 });
104
112
105 $([IPython.events]).on('status_interrupting.Kernel',function () {
113 this.events.on('status_dead.Kernel',function () {
114 that.save_widget.update_document_title();
115 knw.danger("Dead kernel");
116 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
117 });
118
119 this.events.on('status_interrupting.Kernel',function () {
106 knw.set_message("Interrupting kernel", 2000);
120 knw.set_message("Interrupting kernel", 2000);
107 });
121 });
108
122
@@ -110,28 +124,32 b' var IPython = (function (IPython) {'
110 // When the kernel_info reply arrives, the kernel is idle.
124 // When the kernel_info reply arrives, the kernel is idle.
111 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
125 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
112
126
113 $([IPython.events]).on('status_started.Kernel', function (evt, data) {
127 this.events.on('status_started.Kernel', function (evt, data) {
128 knw.info("Websockets Connected", 500);
129 that.events.trigger('status_busy.Kernel');
114 data.kernel.kernel_info(function () {
130 data.kernel.kernel_info(function () {
115 $([IPython.events]).trigger('status_idle.Kernel');
131 that.events.trigger('status_idle.Kernel');
116 });
132 });
117 });
133 });
118
134
119 $([IPython.events]).on('status_dead.Kernel',function () {
135 this.events.on('status_dead.Kernel',function () {
120 var msg = 'The kernel has died, and the automatic restart has failed.' +
136 var msg = 'The kernel has died, and the automatic restart has failed.' +
121 ' It is possible the kernel cannot be restarted.' +
137 ' It is possible the kernel cannot be restarted.' +
122 ' If you are not able to restart the kernel, you will still be able to save' +
138 ' If you are not able to restart the kernel, you will still be able to save' +
123 ' the notebook, but running code will no longer work until the notebook' +
139 ' the notebook, but running code will no longer work until the notebook' +
124 ' is reopened.';
140 ' is reopened.';
125
141
126 IPython.dialog.modal({
142 dialog.modal({
127 title: "Dead kernel",
143 title: "Dead kernel",
128 body : msg,
144 body : msg,
145 keyboard_manager: that.keyboard_manager,
146 notebook: that.notebook,
129 buttons : {
147 buttons : {
130 "Manual Restart": {
148 "Manual Restart": {
131 class: "btn-danger",
149 class: "btn-danger",
132 click: function () {
150 click: function () {
133 $([IPython.events]).trigger('status_restarting.Kernel');
151 that.events.trigger('status_restarting.Kernel');
134 IPython.notebook.start_kernel();
152 that.notebook.start_kernel();
135 }
153 }
136 },
154 },
137 "Don't restart": {}
155 "Don't restart": {}
@@ -139,13 +157,18 b' var IPython = (function (IPython) {'
139 });
157 });
140 });
158 });
141
159
142 $([IPython.events]).on('websocket_closed.Kernel', function (event, data) {
160 this.events.on('websocket_closed.Kernel', function (event, data) {
143 var kernel = data.kernel;
161 var kernel = data.kernel;
144 var ws_url = data.ws_url;
162 var ws_url = data.ws_url;
145 var early = data.early;
163 var early = data.early;
146 var msg;
164 var msg;
165
166 $kernel_ind_icon
167 .attr('class', 'kernel_disconnected_icon')
168 .attr('title', 'No Connection to Kernel');
169
147 if (!early) {
170 if (!early) {
148 knw.set_message('Reconnecting WebSockets', 1000);
171 knw.warning('Reconnecting');
149 setTimeout(function () {
172 setTimeout(function () {
150 kernel.start_channels();
173 kernel.start_channels();
151 }, 5000);
174 }, 5000);
@@ -155,14 +178,16 b' var IPython = (function (IPython) {'
155 msg = "A WebSocket connection could not be established." +
178 msg = "A WebSocket connection could not be established." +
156 " You will NOT be able to run code. Check your" +
179 " You will NOT be able to run code. Check your" +
157 " network connection or notebook server configuration.";
180 " network connection or notebook server configuration.";
158 IPython.dialog.modal({
181 dialog.modal({
159 title: "WebSocket connection failed",
182 title: "WebSocket connection failed",
160 body: msg,
183 body: msg,
184 keyboard_manager: that.keyboard_manager,
185 notebook: that.notebook,
161 buttons : {
186 buttons : {
162 "OK": {},
187 "OK": {},
163 "Reconnect": {
188 "Reconnect": {
164 click: function () {
189 click: function () {
165 knw.set_message('Reconnecting WebSockets', 1000);
190 knw.warning('Reconnecting');
166 setTimeout(function () {
191 setTimeout(function () {
167 kernel.start_channels();
192 kernel.start_channels();
168 }, 5000);
193 }, 5000);
@@ -176,52 +201,52 b' var IPython = (function (IPython) {'
176 var nnw = this.new_notification_widget('notebook');
201 var nnw = this.new_notification_widget('notebook');
177
202
178 // Notebook events
203 // Notebook events
179 $([IPython.events]).on('notebook_loading.Notebook', function () {
204 this.events.on('notebook_loading.Notebook', function () {
180 nnw.set_message("Loading notebook",500);
205 nnw.set_message("Loading notebook",500);
181 });
206 });
182 $([IPython.events]).on('notebook_loaded.Notebook', function () {
207 this.events.on('notebook_loaded.Notebook', function () {
183 nnw.set_message("Notebook loaded",500);
208 nnw.set_message("Notebook loaded",500);
184 });
209 });
185 $([IPython.events]).on('notebook_saving.Notebook', function () {
210 this.events.on('notebook_saving.Notebook', function () {
186 nnw.set_message("Saving notebook",500);
211 nnw.set_message("Saving notebook",500);
187 });
212 });
188 $([IPython.events]).on('notebook_saved.Notebook', function () {
213 this.events.on('notebook_saved.Notebook', function () {
189 nnw.set_message("Notebook saved",2000);
214 nnw.set_message("Notebook saved",2000);
190 });
215 });
191 $([IPython.events]).on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
216 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
192 nnw.set_message(data || "Notebook save failed");
217 nnw.warning(data || "Notebook save failed");
193 });
218 });
194
219
195 // Checkpoint events
220 // Checkpoint events
196 $([IPython.events]).on('checkpoint_created.Notebook', function (evt, data) {
221 this.events.on('checkpoint_created.Notebook', function (evt, data) {
197 var msg = "Checkpoint created";
222 var msg = "Checkpoint created";
198 if (data.last_modified) {
223 if (data.last_modified) {
199 var d = new Date(data.last_modified);
224 var d = new Date(data.last_modified);
200 msg = msg + ": " + d.format("HH:MM:ss");
225 msg = msg + ": " + moment(d).format("HH:mm:ss");
201 }
226 }
202 nnw.set_message(msg, 2000);
227 nnw.set_message(msg, 2000);
203 });
228 });
204 $([IPython.events]).on('checkpoint_failed.Notebook', function () {
229 this.events.on('checkpoint_failed.Notebook', function () {
205 nnw.set_message("Checkpoint failed");
230 nnw.warning("Checkpoint failed");
206 });
231 });
207 $([IPython.events]).on('checkpoint_deleted.Notebook', function () {
232 this.events.on('checkpoint_deleted.Notebook', function () {
208 nnw.set_message("Checkpoint deleted", 500);
233 nnw.set_message("Checkpoint deleted", 500);
209 });
234 });
210 $([IPython.events]).on('checkpoint_delete_failed.Notebook', function () {
235 this.events.on('checkpoint_delete_failed.Notebook', function () {
211 nnw.set_message("Checkpoint delete failed");
236 nnw.warning("Checkpoint delete failed");
212 });
237 });
213 $([IPython.events]).on('checkpoint_restoring.Notebook', function () {
238 this.events.on('checkpoint_restoring.Notebook', function () {
214 nnw.set_message("Restoring to checkpoint...", 500);
239 nnw.set_message("Restoring to checkpoint...", 500);
215 });
240 });
216 $([IPython.events]).on('checkpoint_restore_failed.Notebook', function () {
241 this.events.on('checkpoint_restore_failed.Notebook', function () {
217 nnw.set_message("Checkpoint restore failed");
242 nnw.warning("Checkpoint restore failed");
218 });
243 });
219
244
220 // Autosave events
245 // Autosave events
221 $([IPython.events]).on('autosave_disabled.Notebook', function () {
246 this.events.on('autosave_disabled.Notebook', function () {
222 nnw.set_message("Autosave disabled", 2000);
247 nnw.set_message("Autosave disabled", 2000);
223 });
248 });
224 $([IPython.events]).on('autosave_enabled.Notebook', function (evt, interval) {
249 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
225 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
250 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
226 });
251 });
227
252
@@ -229,7 +254,5 b' var IPython = (function (IPython) {'
229
254
230 IPython.NotificationArea = NotificationArea;
255 IPython.NotificationArea = NotificationArea;
231
256
232 return IPython;
257 return {'NotificationArea': NotificationArea};
233
258 });
234 }(IPython));
235
@@ -1,18 +1,11 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Notification widget
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 ], function(IPython, $) {
12 var IPython = (function (IPython) {
13 "use strict";
8 "use strict";
14 var utils = IPython.utils;
15
16
9
17 var NotificationWidget = function (selector) {
10 var NotificationWidget = function (selector) {
18 this.selector = selector;
11 this.selector = selector;
@@ -22,7 +15,6 b' var IPython = (function (IPython) {'
22 this.element = $(selector);
15 this.element = $(selector);
23 this.style();
16 this.style();
24 }
17 }
25 this.element.button();
26 this.element.hide();
18 this.element.hide();
27 var that = this;
19 var that = this;
28
20
@@ -31,10 +23,8 b' var IPython = (function (IPython) {'
31
23
32 };
24 };
33
25
34
35 NotificationWidget.prototype.style = function () {
26 NotificationWidget.prototype.style = function () {
36 this.element.addClass('notification_widget pull-right');
27 this.element.addClass('notification_widget');
37 this.element.addClass('border-box-sizing');
38 };
28 };
39
29
40 // msg : message to display
30 // msg : message to display
@@ -43,14 +33,24 b' var IPython = (function (IPython) {'
43 // if timeout <= 0
33 // if timeout <= 0
44 // click_callback : function called if user click on notification
34 // click_callback : function called if user click on notification
45 // could return false to prevent the notification to be dismissed
35 // could return false to prevent the notification to be dismissed
46 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, opts) {
36 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) {
47 var opts = opts || {};
37 var options = options || {};
48 var callback = click_callback || function() {return false;};
38 var callback = click_callback || function() {return true;};
49 var that = this;
39 var that = this;
50 this.inner.attr('class', opts.icon);
40 // unbind potential previous callback
51 this.inner.attr('title', opts.title);
41 this.element.unbind('click');
42 this.inner.attr('class', options.icon);
43 this.inner.attr('title', options.title);
52 this.inner.text(msg);
44 this.inner.text(msg);
53 this.element.fadeIn(100);
45 this.element.fadeIn(100);
46
47 // reset previous set style
48 this.element.removeClass();
49 this.style();
50 if (options.class){
51
52 this.element.addClass(options.class)
53 }
54 if (this.timeout !== null) {
54 if (this.timeout !== null) {
55 clearTimeout(this.timeout);
55 clearTimeout(this.timeout);
56 this.timeout = null;
56 this.timeout = null;
@@ -62,7 +62,7 b' var IPython = (function (IPython) {'
62 }, timeout);
62 }, timeout);
63 } else {
63 } else {
64 this.element.click(function() {
64 this.element.click(function() {
65 if( callback() != false ) {
65 if( callback() !== false ) {
66 that.element.fadeOut(100, function () {that.inner.text('');});
66 that.element.fadeOut(100, function () {that.inner.text('');});
67 that.element.unbind('click');
67 that.element.unbind('click');
68 }
68 }
@@ -75,14 +75,30 b' var IPython = (function (IPython) {'
75 };
75 };
76
76
77
77
78 NotificationWidget.prototype.info = function (msg, timeout, click_callback, options) {
79 var options = options || {};
80 options.class = options.class +' info';
81 var timeout = timeout || 3500;
82 this.set_message(msg, timeout, click_callback, options);
83 }
84 NotificationWidget.prototype.warning = function (msg, timeout, click_callback, options) {
85 var options = options || {};
86 options.class = options.class +' warning';
87 this.set_message(msg, timeout, click_callback, options);
88 }
89 NotificationWidget.prototype.danger = function (msg, timeout, click_callback, options) {
90 var options = options || {};
91 options.class = options.class +' danger';
92 this.set_message(msg, timeout, click_callback, options);
93 }
94
95
78 NotificationWidget.prototype.get_message = function () {
96 NotificationWidget.prototype.get_message = function () {
79 return this.inner.html();
97 return this.inner.html();
80 };
98 };
81
99
82
100 // For backwards compatibility.
83 IPython.NotificationWidget = NotificationWidget;
101 IPython.NotificationWidget = NotificationWidget;
84
102
85 return IPython;
103 return {'NotificationWidget': NotificationWidget};
86
104 });
87 }(IPython));
88
@@ -1,38 +1,37 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 //============================================================================
4 define([
5 // OutputArea
5 'base/js/namespace',
6 //============================================================================
6 'jqueryui',
7
7 'base/js/utils',
8 /**
8 'base/js/security',
9 * @module IPython
9 'base/js/keyboard',
10 * @namespace IPython
10 'notebook/js/mathjaxutils',
11 * @submodule OutputArea
11 'components/marked/lib/marked',
12 */
12 ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
13 var IPython = (function (IPython) {
14 "use strict";
13 "use strict";
15
14
16 var utils = IPython.utils;
17
18 /**
15 /**
19 * @class OutputArea
16 * @class OutputArea
20 *
17 *
21 * @constructor
18 * @constructor
22 */
19 */
23
20
24 var OutputArea = function (selector, prompt_area) {
21 var OutputArea = function (options) {
25 this.selector = selector;
22 this.selector = options.selector;
26 this.wrapper = $(selector);
23 this.events = options.events;
24 this.keyboard_manager = options.keyboard_manager;
25 this.wrapper = $(options.selector);
27 this.outputs = [];
26 this.outputs = [];
28 this.collapsed = false;
27 this.collapsed = false;
29 this.scrolled = false;
28 this.scrolled = false;
30 this.trusted = true;
29 this.trusted = true;
31 this.clear_queued = null;
30 this.clear_queued = null;
32 if (prompt_area === undefined) {
31 if (options.prompt_area === undefined) {
33 this.prompt_area = true;
32 this.prompt_area = true;
34 } else {
33 } else {
35 this.prompt_area = prompt_area;
34 this.prompt_area = options.prompt_area;
36 }
35 }
37 this.create_elements();
36 this.create_elements();
38 this.style();
37 this.style();
@@ -101,7 +100,7 b' var IPython = (function (IPython) {'
101
100
102 this.element.resize(function () {
101 this.element.resize(function () {
103 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
104 if ( IPython.utils.browser[0] === "Firefox" ) {
103 if ( utils.browser[0] === "Firefox" ) {
105 return;
104 return;
106 }
105 }
107 // maybe scroll output,
106 // maybe scroll output,
@@ -282,12 +281,15 b' var IPython = (function (IPython) {'
282 needs_height_reset = true;
281 needs_height_reset = true;
283 }
282 }
284
283
284 var record_output = true;
285
285 if (json.output_type === 'execute_result') {
286 if (json.output_type === 'execute_result') {
286 this.append_execute_result(json);
287 this.append_execute_result(json);
287 } else if (json.output_type === 'error') {
288 } else if (json.output_type === 'error') {
288 this.append_error(json);
289 this.append_error(json);
289 } else if (json.output_type === 'stream') {
290 } else if (json.output_type === 'stream') {
290 this.append_stream(json);
291 // append_stream might have merged the output with earlier stream output
292 record_output = this.append_stream(json);
291 }
293 }
292
294
293 // We must release the animation fixed height in a callback since Gecko
295 // We must release the animation fixed height in a callback since Gecko
@@ -308,7 +310,9 b' var IPython = (function (IPython) {'
308 handle_appended();
310 handle_appended();
309 }
311 }
310
312
311 this.outputs.push(json);
313 if (record_output) {
314 this.outputs.push(json);
315 }
312 };
316 };
313
317
314
318
@@ -459,20 +463,23 b' var IPython = (function (IPython) {'
459 // latest output was in the same stream,
463 // latest output was in the same stream,
460 // so append directly into its pre tag
464 // so append directly into its pre tag
461 // escape ANSI & HTML specials:
465 // escape ANSI & HTML specials:
466 last.text = utils.fixCarriageReturn(last.text + json.text);
462 var pre = this.element.find('div.'+subclass).last().find('pre');
467 var pre = this.element.find('div.'+subclass).last().find('pre');
463 var html = utils.fixCarriageReturn(
468 var html = utils.fixConsole(last.text);
464 pre.html() + utils.fixConsole(text));
465 // The only user content injected with this HTML call is
469 // The only user content injected with this HTML call is
466 // escaped by the fixConsole() method.
470 // escaped by the fixConsole() method.
467 pre.html(html);
471 pre.html(html);
468 return;
472 // return false signals that we merged this output with the previous one,
473 // and the new output shouldn't be recorded.
474 return false;
469 }
475 }
470 }
476 }
471
477
472 if (!text.replace("\r", "")) {
478 if (!text.replace("\r", "")) {
473 // text is nothing (empty string, \r, etc.)
479 // text is nothing (empty string, \r, etc.)
474 // so don't append any elements, which might add undesirable space
480 // so don't append any elements, which might add undesirable space
475 return;
481 // return true to indicate the output should be recorded.
482 return true;
476 }
483 }
477
484
478 // If we got here, attach a new div
485 // If we got here, attach a new div
@@ -482,6 +489,7 b' var IPython = (function (IPython) {'
482 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
489 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
483 }
490 }
484 this._safe_append(toinsert);
491 this._safe_append(toinsert);
492 return true;
485 };
493 };
486
494
487
495
@@ -515,7 +523,7 b' var IPython = (function (IPython) {'
515 if (!this.trusted && !OutputArea.safe_outputs[type]) {
523 if (!this.trusted && !OutputArea.safe_outputs[type]) {
516 // not trusted, sanitize HTML
524 // not trusted, sanitize HTML
517 if (type==='text/html' || type==='text/svg') {
525 if (type==='text/html' || type==='text/svg') {
518 value = IPython.security.sanitize_html(value);
526 value = security.sanitize_html(value);
519 } else {
527 } else {
520 // don't display if we don't know how to sanitize it
528 // don't display if we don't know how to sanitize it
521 console.log("Ignoring untrusted " + type + " output.");
529 console.log("Ignoring untrusted " + type + " output.");
@@ -531,7 +539,7 b' var IPython = (function (IPython) {'
531 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
539 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
532 setTimeout(handle_inserted, 0);
540 setTimeout(handle_inserted, 0);
533 }
541 }
534 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
542 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
535 return toinsert;
543 return toinsert;
536 }
544 }
537 }
545 }
@@ -542,7 +550,7 b' var IPython = (function (IPython) {'
542 var append_html = function (html, md, element) {
550 var append_html = function (html, md, element) {
543 var type = 'text/html';
551 var type = 'text/html';
544 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
552 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
545 IPython.keyboard_manager.register_events(toinsert);
553 this.keyboard_manager.register_events(toinsert);
546 toinsert.append(html);
554 toinsert.append(html);
547 element.append(toinsert);
555 element.append(toinsert);
548 return toinsert;
556 return toinsert;
@@ -552,11 +560,11 b' var IPython = (function (IPython) {'
552 var append_markdown = function(markdown, md, element) {
560 var append_markdown = function(markdown, md, element) {
553 var type = 'text/markdown';
561 var type = 'text/markdown';
554 var toinsert = this.create_output_subarea(md, "output_markdown", type);
562 var toinsert = this.create_output_subarea(md, "output_markdown", type);
555 var text_and_math = IPython.mathjaxutils.remove_math(markdown);
563 var text_and_math = mathjaxutils.remove_math(markdown);
556 var text = text_and_math[0];
564 var text = text_and_math[0];
557 var math = text_and_math[1];
565 var math = text_and_math[1];
558 var html = marked.parser(marked.lexer(text));
566 var html = marked.parser(marked.lexer(text));
559 html = IPython.mathjaxutils.replace_math(html, math);
567 html = mathjaxutils.replace_math(html, math);
560 toinsert.append(html);
568 toinsert.append(html);
561 element.append(toinsert);
569 element.append(toinsert);
562 return toinsert;
570 return toinsert;
@@ -567,13 +575,8 b' var IPython = (function (IPython) {'
567 // We just eval the JS code, element appears in the local scope.
575 // We just eval the JS code, element appears in the local scope.
568 var type = 'application/javascript';
576 var type = 'application/javascript';
569 var toinsert = this.create_output_subarea(md, "output_javascript", type);
577 var toinsert = this.create_output_subarea(md, "output_javascript", type);
570 IPython.keyboard_manager.register_events(toinsert);
578 this.keyboard_manager.register_events(toinsert);
571 element.append(toinsert);
579 element.append(toinsert);
572 // FIXME TODO : remove `container element for 3.0`
573 //backward compat, js should be eval'ed in a context where `container` is defined.
574 var container = element;
575 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
576 // end backward compat
577
580
578 // Fix for ipython/issues/5293, make sure `element` is the area which
581 // Fix for ipython/issues/5293, make sure `element` is the area which
579 // output can be inserted into at the time of JS execution.
582 // output can be inserted into at the time of JS execution.
@@ -763,7 +766,7 b' var IPython = (function (IPython) {'
763 .keydown(function (event, ui) {
766 .keydown(function (event, ui) {
764 // make sure we submit on enter,
767 // make sure we submit on enter,
765 // and don't re-execute the *cell* on shift-enter
768 // and don't re-execute the *cell* on shift-enter
766 if (event.which === IPython.keyboard.keycodes.enter) {
769 if (event.which === keyboard.keycodes.enter) {
767 that._submit_raw_input();
770 that._submit_raw_input();
768 return false;
771 return false;
769 }
772 }
@@ -775,7 +778,7 b' var IPython = (function (IPython) {'
775 var raw_input = area.find('input.raw_input');
778 var raw_input = area.find('input.raw_input');
776 // Register events that enable/disable the keyboard manager while raw
779 // Register events that enable/disable the keyboard manager while raw
777 // input is focused.
780 // input is focused.
778 IPython.keyboard_manager.register_events(raw_input);
781 this.keyboard_manager.register_events(raw_input);
779 // Note, the following line used to read raw_input.focus().focus().
782 // Note, the following line used to read raw_input.focus().focus().
780 // This seemed to be needed otherwise only the cell would be focused.
783 // This seemed to be needed otherwise only the cell would be focused.
781 // But with the modal UI, this seems to work fine with one call to focus().
784 // But with the modal UI, this seems to work fine with one call to focus().
@@ -794,14 +797,14 b' var IPython = (function (IPython) {'
794 }
797 }
795 var content = {
798 var content = {
796 output_type : 'stream',
799 output_type : 'stream',
797 name : 'stdout',
800 stream : 'stdout',
798 text : theprompt.text() + echo + '\n'
801 text : theprompt.text() + echo + '\n'
799 }
802 }
800 // remove form container
803 // remove form container
801 container.parent().remove();
804 container.parent().remove();
802 // replace with plaintext version in stdout
805 // replace with plaintext version in stdout
803 this.append_output(content, false);
806 this.append_output(content, false);
804 $([IPython.events]).trigger('send_input_reply.Kernel', value);
807 this.events.trigger('send_input_reply.Kernel', value);
805 }
808 }
806
809
807
810
@@ -992,8 +995,8 b' var IPython = (function (IPython) {'
992 "application/pdf" : append_pdf
995 "application/pdf" : append_pdf
993 };
996 };
994
997
998 // For backwards compatability.
995 IPython.OutputArea = OutputArea;
999 IPython.OutputArea = OutputArea;
996
1000
997 return IPython;
1001 return {'OutputArea': OutputArea};
998
1002 });
999 }(IPython));
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from IPython/html/static/widgets/js/widget_container.js to IPython/html/static/widgets/js/widget_box.js
NO CONTENT: file renamed from IPython/html/static/widgets/js/widget_container.js to IPython/html/static/widgets/js/widget_box.js
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from IPython/html/tests/widgets/widget_container.js to IPython/html/tests/widgets/widget_box.js
NO CONTENT: file renamed from IPython/html/tests/widgets/widget_container.js to IPython/html/tests/widgets/widget_box.js
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from examples/Notebook/Animations Using clear_output.ipynb to examples/IPython Kernel/Animations Using clear_output.ipynb
NO CONTENT: file renamed from examples/Notebook/Animations Using clear_output.ipynb to examples/IPython Kernel/Animations Using clear_output.ipynb
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from examples/Notebook/Plotting with Matplotlib.ipynb to examples/IPython Kernel/Plotting in the Notebook.ipynb
NO CONTENT: file renamed from examples/Notebook/Plotting with Matplotlib.ipynb to examples/IPython Kernel/Plotting in the Notebook.ipynb
1 NO CONTENT: file renamed from examples/Notebook/Raw Input.ipynb to examples/IPython Kernel/Raw Input in the Notebook.ipynb
NO CONTENT: file renamed from examples/Notebook/Raw Input.ipynb to examples/IPython Kernel/Raw Input in the Notebook.ipynb
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from examples/Notebook/Display System.ipynb to examples/IPython Kernel/Rich Output.ipynb
NO CONTENT: file renamed from examples/Notebook/Display System.ipynb to examples/IPython Kernel/Rich Output.ipynb
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from examples/Notebook/SymPy.ipynb to examples/IPython Kernel/SymPy.ipynb
NO CONTENT: file renamed from examples/Notebook/SymPy.ipynb to examples/IPython Kernel/SymPy.ipynb
1 NO CONTENT: file renamed from examples/Interactive Widgets/Widget Styles.ipynb to examples/IPython Kernel/Terminal Usage.ipynb
NO CONTENT: file renamed from examples/Interactive Widgets/Widget Styles.ipynb to examples/IPython Kernel/Terminal Usage.ipynb
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from examples/Notebook/Trapezoid Rule.ipynb to examples/IPython Kernel/Trapezoid Rule.ipynb
NO CONTENT: file renamed from examples/Notebook/Trapezoid Rule.ipynb to examples/IPython Kernel/Trapezoid Rule.ipynb
1 NO CONTENT: file renamed from examples/Interactive Widgets/Custom Widgets.ipynb to examples/Interactive Widgets/Date Picker Widget.ipynb
NO CONTENT: file renamed from examples/Interactive Widgets/Custom Widgets.ipynb to examples/Interactive Widgets/Date Picker Widget.ipynb
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from examples/Notebook/User Interface.ipynb to examples/Notebook/Running the Notebook Server.ipynb
NO CONTENT: file renamed from examples/Notebook/User Interface.ipynb to examples/Notebook/Running the Notebook Server.ipynb
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from examples/Notebook/Typesetting Math Using MathJax.ipynb to examples/Notebook/Typesetting Equations.ipynb
NO CONTENT: file renamed from examples/Notebook/Typesetting Math Using MathJax.ipynb to examples/Notebook/Typesetting Equations.ipynb
1 NO CONTENT: file renamed from examples/Notebook/Markdown Cells.ipynb to examples/Notebook/Working With Markdown Cells.ipynb
NO CONTENT: file renamed from examples/Notebook/Markdown Cells.ipynb to examples/Notebook/Working With Markdown Cells.ipynb
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from examples/Notebook/images/animation.m4v to examples/images/animation.m4v
NO CONTENT: file renamed from examples/Notebook/images/animation.m4v to examples/images/animation.m4v
1 NO CONTENT: file renamed from examples/Notebook/images/python_logo.svg to examples/images/python_logo.svg
NO CONTENT: file renamed from examples/Notebook/images/python_logo.svg to examples/images/python_logo.svg
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now