##// 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
1 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
1 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
1 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
1 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
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
1 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
1 NO CONTENT: new file 100644, binary diff hidden
1 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 7 docs/source/config/options
8 8 docs/gh-pages
9 9 IPython/html/notebook/static/mathjax
10 IPython/html/static/style/*.map
10 11 *.py[co]
11 12 __pycache__
12 13 *.egg-info
@@ -13,8 +13,10 b' before_install:'
13 13 # Pierre Carrier's PPA for PhantomJS and CasperJS
14 14 - time sudo add-apt-repository -y ppa:pcarrier/ppa
15 15 - time sudo apt-get update
16 - time sudo apt-get install pandoc casperjs nodejs libzmq3-dev
17 - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests mock pyzmq jsonschema jsonpointer
16 - time sudo apt-get install pandoc casperjs libzmq3-dev
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 20 install:
19 21 - time python setup.py install -q
20 22 script:
@@ -1,31 +1,11 b''
1 1 # encoding: utf-8
2 """
3 A base class for objects that are configurable.
2 """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 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 9 import logging
30 10 from copy import deepcopy
31 11
@@ -375,16 +355,12 b' class LoggingConfigurable(Configurable):'
375 355 """A parent class for Configurables that log.
376 356
377 357 Subclasses have a log trait, and the default behavior
378 is to get the logger from the currently running Application
379 via Application.instance().log.
358 is to get the logger from the currently running Application.
380 359 """
381 360
382 361 log = Instance('logging.Logger')
383 362 def _log_default(self):
384 from IPython.config.application import Application
385 if Application.initialized():
386 return Application.instance().log
387 else:
388 return logging.getLogger()
363 from IPython.utils import log
364 return log.get_logger()
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
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 #-----------------------------------------------------------------------------
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
25 6
26 7 import argparse
27 8 import copy
@@ -308,11 +289,8 b' class ConfigLoader(object):'
308 289 """
309 290
310 291 def _log_default(self):
311 from IPython.config.application import Application
312 if Application.initialized():
313 return Application.instance().log
314 else:
315 return logging.getLogger()
292 from IPython.utils.log import get_logger
293 return get_logger()
316 294
317 295 def __init__(self, log=None):
318 296 """A base class for config loaders.
@@ -165,8 +165,6 b' class IPythonConsoleApp(ConnectionFileMixin):'
165 165 if argv is None:
166 166 argv = sys.argv[1:]
167 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 169 def init_connection_file(self):
172 170 """find the connection file, and load the info if found.
@@ -289,6 +287,7 b' class IPythonConsoleApp(ConnectionFileMixin):'
289 287 try:
290 288 self.kernel_manager = self.kernel_manager_class(
291 289 ip=self.ip,
290 session=self.session,
292 291 transport=self.transport,
293 292 shell_port=self.shell_port,
294 293 iopub_port=self.iopub_port,
@@ -326,6 +325,7 b' class IPythonConsoleApp(ConnectionFileMixin):'
326 325 self.kernel_client = self.kernel_manager.client()
327 326 else:
328 327 self.kernel_client = self.kernel_client_class(
328 session=self.session,
329 329 ip=self.ip,
330 330 transport=self.transport,
331 331 shell_port=self.shell_port,
@@ -80,6 +80,7 b' from IPython.core.error import TryNext'
80 80 from IPython.core.inputsplitter import ESC_MAGIC
81 81 from IPython.utils import generics
82 82 from IPython.utils import io
83 from IPython.utils.decorators import undoc
83 84 from IPython.utils.dir2 import dir2
84 85 from IPython.utils.process import arg_split
85 86 from IPython.utils.py3compat import builtin_mod, string_types
@@ -216,7 +217,7 b' def penalize_magics_key(word):'
216 217 return word
217 218
218 219
219
220 @undoc
220 221 class Bunch(object): pass
221 222
222 223
@@ -865,6 +866,7 b' class IPCompleter(Completer):'
865 866 return argMatches
866 867
867 868 def dict_key_matches(self, text):
869 "Match string keys in a dictionary, after e.g. 'foo[' "
868 870 def get_keys(obj):
869 871 # Only allow completion for known in-memory dict-like types
870 872 if isinstance(obj, dict) or\
@@ -1010,9 +1012,6 b' class IPCompleter(Completer):'
1010 1012 def complete(self, text=None, line_buffer=None, cursor_pos=None):
1011 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 1015 Note that both the text and the line_buffer are optional, but at least
1017 1016 one of them must be given.
1018 1017
@@ -26,7 +26,13 b' from IPython.core.formatters import _safe_get_formatter_method'
26 26 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
27 27 unicode_type)
28 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 38 # utility functions
@@ -78,6 +84,48 b' def _display_mimetype(mimetype, objs, raw=False, metadata=None):'
78 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 129 def display(*objs, **kwargs):
82 130 """Display a Python object in all frontends.
83 131
@@ -19,9 +19,11 b' from __future__ import print_function'
19 19
20 20 from IPython.config.configurable import Configurable
21 21 from IPython.utils import io
22 from IPython.utils.py3compat import string_types
23 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 28 # Main payload class
27 29 #-----------------------------------------------------------------------------
@@ -112,48 +114,3 b' class CapturingDisplayPublisher(DisplayPublisher):'
112 114
113 115 # empty the list, *do not* reassign a new list
114 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 736 # stdlib venv may symlink sys.executable, so we can't use realpath.
737 737 # but others can symlink *to* the venv Python, so we can't just use sys.executable.
738 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 740 paths = [p]
741 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 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 746 # Running properly in the virtualenv, don't need to do anything
746 747 return
747 748
@@ -910,7 +911,8 b' class InteractiveShell(SingletonConfigurable):'
910 911 try:
911 912 main_mod = self._main_mod_cache[filename]
912 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 916 doc="Module created for script run in IPython")
915 917 else:
916 918 main_mod.__dict__.clear()
@@ -1735,7 +1737,7 b' class InteractiveShell(SingletonConfigurable):'
1735 1737 This hook should be used sparingly, only in places which are not likely
1736 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 1742 def _get_exc_info(self, exc_tuple=None):
1741 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 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 1782 exception_only=False):
1781 1783 """Display the exception that just occurred.
1782 1784
@@ -2918,10 +2920,9 b' class InteractiveShell(SingletonConfigurable):'
2918 2920 False : successful execution.
2919 2921 True : an error occurred.
2920 2922 """
2921
2922 2923 # Set our own excepthook in case the user code tries to call it
2923 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 2927 # we save the original sys.excepthook in the instance, in case config
2927 2928 # code (such as magics) needs access to it.
@@ -2939,8 +2940,8 b' class InteractiveShell(SingletonConfigurable):'
2939 2940 self.showtraceback(exception_only=True)
2940 2941 warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1)
2941 2942 except self.custom_exceptions:
2942 etype,value,tb = sys.exc_info()
2943 self.CustomTB(etype,value,tb)
2943 etype, value, tb = sys.exc_info()
2944 self.CustomTB(etype, value, tb)
2944 2945 except:
2945 2946 self.showtraceback()
2946 2947 else:
@@ -3087,6 +3088,7 b' class InteractiveShell(SingletonConfigurable):'
3087 3088 self.tempdirs.append(dirname)
3088 3089
3089 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 3092 self.tempfiles.append(filename)
3091 3093
3092 3094 if data:
@@ -193,6 +193,8 b' class ScriptMagics(Magics):'
193 193 else:
194 194 raise
195 195
196 if not cell.endswith('\n'):
197 cell += '\n'
196 198 cell = cell.encode('utf8', 'replace')
197 199 if args.bg:
198 200 self.bg_processes.append(p)
@@ -6,6 +6,7 b''
6 6 #-----------------------------------------------------------------------------
7 7
8 8 # stdlib
9 import io
9 10 import os
10 11 import sys
11 12 import tempfile
@@ -124,7 +125,7 b' def test_history():'
124 125 # Cross testing: check that magic %save can get previous session.
125 126 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
126 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 129 nt.assert_equal(testfile.read(),
129 130 u"# coding: utf-8\n" + u"\n".join(hist)+u"\n")
130 131
@@ -462,6 +462,21 b' class InteractiveShellTestCase(unittest.TestCase):'
462 462 ip.run_cell("d = 1/2", shell_futures=True)
463 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 481 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
467 482
@@ -9,6 +9,7 b' from IPython.testing import tools as tt'
9 9 from IPython.testing.decorators import onlyif_unicode_paths
10 10 from IPython.utils.syspathcontext import prepended_to_syspath
11 11 from IPython.utils.tempdir import TemporaryDirectory
12 from IPython.utils.py3compat import PY3
12 13
13 14 ip = get_ipython()
14 15
@@ -147,3 +148,37 b' class SyntaxErrorTest(unittest.TestCase):'
147 148 except ValueError:
148 149 with tt.AssertPrints('QWERTY'):
149 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 39 Verbose).
40 40
41 41
42 Installation instructions for ColorTB::
42 Installation instructions for VerboseTB::
43 43
44 44 import sys,ultratb
45 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>
77 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
76 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.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
80 # the file COPYING, distributed as part of this software.
79 # Distributed under the terms of the BSD License. The full license is in
80 # the file COPYING, distributed as part of this software.
81 81 #*****************************************************************************
82 82
83 83 from __future__ import unicode_literals
@@ -95,14 +95,14 b' import tokenize'
95 95 import traceback
96 96 import types
97 97
98 try: # Python 2
98 try: # Python 2
99 99 generate_tokens = tokenize.generate_tokens
100 except AttributeError: # Python 3
100 except AttributeError: # Python 3
101 101 generate_tokens = tokenize.tokenize
102 102
103 103 # For purposes of monkeypatching inspect to fix a bug in it.
104 from inspect import getsourcefile, getfile, getmodule,\
105 ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode
104 from inspect import getsourcefile, getfile, getmodule, \
105 ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode
106 106
107 107 # IPython's own modules
108 108 # Modified pdb which doesn't damage IPython's readline handling
@@ -125,11 +125,11 b' INDENT_SIZE = 8'
125 125
126 126 # Default color scheme. This is used, for example, by the traceback
127 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 129 # to users of ultratb who are NOT running inside ipython.
130 130 DEFAULT_SCHEME = 'NoColor'
131 131
132 #---------------------------------------------------------------------------
132 # ---------------------------------------------------------------------------
133 133 # Code begins
134 134
135 135 # Utility functions
@@ -141,6 +141,7 b' def inspect_error():'
141 141 error('Internal Python error in the inspect module.\n'
142 142 'Below is the traceback from this internal error.\n')
143 143
144
144 145 # This function is a monkeypatch we apply to the Python inspect module. We have
145 146 # now found when it's needed (see discussion on issue gh-1456), and we have a
146 147 # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if
@@ -212,7 +213,7 b' def findsource(object):'
212 213 pmatch = pat.match
213 214 # fperez - fix: sometimes, co_firstlineno can give a number larger than
214 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 217 while lnum > 0:
217 218 if pmatch(lines[lnum]): break
218 219 lnum -= 1
@@ -220,9 +221,11 b' def findsource(object):'
220 221 return lines, lnum
221 222 raise IOError('could not find code object')
222 223
224
223 225 # Monkeypatch inspect to apply our bugfix.
224 226 def with_patch_inspect(f):
225 227 """decorator for monkeypatching inspect.findsource"""
228
226 229 def wrapped(*args, **kwargs):
227 230 save_findsource = inspect.findsource
228 231 inspect.findsource = findsource
@@ -230,8 +233,10 b' def with_patch_inspect(f):'
230 233 return f(*args, **kwargs)
231 234 finally:
232 235 inspect.findsource = save_findsource
236
233 237 return wrapped
234 238
239
235 240 def fix_frame_records_filenames(records):
236 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 260 @with_patch_inspect
256 def _fixed_getinnerframes(etb, context=1,tb_offset=0):
257 LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5
258
259 records = fix_frame_records_filenames(inspect.getinnerframes(etb, context))
261 def _fixed_getinnerframes(etb, context=1, tb_offset=0):
262 LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5
260 263
264 records = fix_frame_records_filenames(inspect.getinnerframes(etb, context))
261 265 # If the error is at the console, don't build any context, since it would
262 266 # otherwise produce 5 blank lines printed out (there is no file at the
263 267 # console)
@@ -272,9 +276,9 b' def _fixed_getinnerframes(etb, context=1,tb_offset=0):'
272 276 aux = traceback.extract_tb(etb)
273 277 assert len(records) == len(aux)
274 278 for i, (file, lnum, _, _) in zip(range(len(records)), aux):
275 maybeStart = lnum-1 - context//2
276 start = max(maybeStart, 0)
277 end = start + context
279 maybeStart = lnum - 1 - context // 2
280 start = max(maybeStart, 0)
281 end = start + context
278 282 lines = ulinecache.getlines(file)[start:end]
279 283 buf = list(records[i])
280 284 buf[LNUM_POS] = lnum
@@ -290,7 +294,8 b' def _fixed_getinnerframes(etb, context=1,tb_offset=0):'
290 294
291 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 299 numbers_width = INDENT_SIZE - 1
295 300 res = []
296 301 i = lnum - index
@@ -315,7 +320,7 b' def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):'
315 320 # This is the line with the error
316 321 pad = numbers_width - len(str(i))
317 322 if pad >= 3:
318 marker = '-'*(pad-3) + '-> '
323 marker = '-' * (pad - 3) + '-> '
319 324 elif pad == 2:
320 325 marker = '> '
321 326 elif pad == 1:
@@ -323,12 +328,12 b' def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):'
323 328 else:
324 329 marker = ''
325 330 num = marker + str(i)
326 line = '%s%s%s %s%s' %(Colors.linenoEm, num,
327 Colors.line, line, Colors.Normal)
331 line = '%s%s%s %s%s' % (Colors.linenoEm, num,
332 Colors.line, line, Colors.Normal)
328 333 else:
329 num = '%*s' % (numbers_width,i)
330 line = '%s%s%s %s' %(Colors.lineno, num,
331 Colors.Normal, line)
334 num = '%*s' % (numbers_width, i)
335 line = '%s%s%s %s' % (Colors.lineno, num,
336 Colors.Normal, line)
332 337
333 338 res.append(line)
334 339 if lvals and i == lnum:
@@ -389,16 +394,16 b' class TBTools(object):'
389 394
390 395 ostream = property(_get_ostream, _set_ostream)
391 396
392 def set_colors(self,*args,**kw):
397 def set_colors(self, *args, **kw):
393 398 """Shorthand access to the color table scheme selector method."""
394 399
395 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 402 # for convenience, set Colors to the active scheme
398 403 self.Colors = self.color_scheme_table.active_colors
399 404 # Also set colors of debugger
400 if hasattr(self,'pdb') and self.pdb is not None:
401 self.pdb.set_colors(*args,**kw)
405 if hasattr(self, 'pdb') and self.pdb is not None:
406 self.pdb.set_colors(*args, **kw)
402 407
403 408 def color_toggle(self):
404 409 """Toggle between the currently active color scheme and NoColor."""
@@ -453,7 +458,7 b' class ListTB(TBTools):'
453 458 Because they are meant to be called without a full traceback (only a
454 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 462 TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
458 463 ostream=ostream)
459 464
@@ -497,7 +502,7 b' class ListTB(TBTools):'
497 502 elist = elist[tb_offset:]
498 503
499 504 out_list.append('Traceback %s(most recent call last)%s:' %
500 (Colors.normalEm, Colors.Normal) + '\n')
505 (Colors.normalEm, Colors.Normal) + '\n')
501 506 out_list.extend(self._format_list(elist))
502 507 # The exception info should be a single entry in the list.
503 508 lines = ''.join(self._format_exception_only(etype, value))
@@ -510,7 +515,7 b' class ListTB(TBTools):'
510 515 ## out_list.append(lines[-1])
511 516
512 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 519 # can restore it.
515 520
516 521 return out_list
@@ -532,25 +537,24 b' class ListTB(TBTools):'
532 537 list = []
533 538 for filename, lineno, name, line in extracted_list[:-1]:
534 539 item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \
535 (Colors.filename, filename, Colors.Normal,
536 Colors.lineno, lineno, Colors.Normal,
537 Colors.name, name, Colors.Normal)
540 (Colors.filename, filename, Colors.Normal,
541 Colors.lineno, lineno, Colors.Normal,
542 Colors.name, name, Colors.Normal)
538 543 if line:
539 544 item += ' %s\n' % line.strip()
540 545 list.append(item)
541 546 # Emphasize the last entry
542 547 filename, lineno, name, line = extracted_list[-1]
543 548 item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \
544 (Colors.normalEm,
545 Colors.filenameEm, filename, Colors.normalEm,
546 Colors.linenoEm, lineno, Colors.normalEm,
547 Colors.nameEm, name, Colors.normalEm,
548 Colors.Normal)
549 (Colors.normalEm,
550 Colors.filenameEm, filename, Colors.normalEm,
551 Colors.linenoEm, lineno, Colors.normalEm,
552 Colors.nameEm, name, Colors.normalEm,
553 Colors.Normal)
549 554 if line:
550 555 item += '%s %s%s\n' % (Colors.line, line.strip(),
551 Colors.Normal)
556 Colors.Normal)
552 557 list.append(item)
553 #from pprint import pformat; print 'LISTTB', pformat(list) # dbg
554 558 return list
555 559
556 560 def _format_exception_only(self, etype, value):
@@ -572,11 +576,10 b' class ListTB(TBTools):'
572 576 stype = Colors.excName + etype.__name__ + Colors.Normal
573 577 if value is None:
574 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 580 else:
577 581 if issubclass(etype, SyntaxError):
578 582 have_filedata = True
579 #print 'filename is',filename # dbg
580 583 if not value.filename: value.filename = "<string>"
581 584 if value.lineno:
582 585 lineno = value.lineno
@@ -585,9 +588,9 b' class ListTB(TBTools):'
585 588 lineno = 'unknown'
586 589 textline = ''
587 590 list.append('%s File %s"%s"%s, line %s%s%s\n' % \
588 (Colors.normalEm,
589 Colors.filenameEm, py3compat.cast_unicode(value.filename), Colors.normalEm,
590 Colors.linenoEm, lineno, Colors.Normal ))
591 (Colors.normalEm,
592 Colors.filenameEm, py3compat.cast_unicode(value.filename), Colors.normalEm,
593 Colors.linenoEm, lineno, Colors.Normal ))
591 594 if textline == '':
592 595 textline = py3compat.cast_unicode(value.text, "utf-8")
593 596
@@ -600,13 +603,13 b' class ListTB(TBTools):'
600 603 Colors.Normal))
601 604 if value.offset is not None:
602 605 s = ' '
603 for c in textline[i:value.offset-1]:
606 for c in textline[i:value.offset - 1]:
604 607 if c.isspace():
605 608 s += c
606 609 else:
607 610 s += ' '
608 611 list.append('%s%s^%s\n' % (Colors.caret, s,
609 Colors.Normal) )
612 Colors.Normal))
610 613
611 614 try:
612 615 s = value.msg
@@ -636,7 +639,6 b' class ListTB(TBTools):'
636 639 """
637 640 return ListTB.structured_traceback(self, etype, value, [])
638 641
639
640 642 def show_exception_only(self, etype, evalue):
641 643 """Only print the exception type and message, without a traceback.
642 644
@@ -659,6 +661,7 b' class ListTB(TBTools):'
659 661 except:
660 662 return '<unprintable %s object>' % type(value).__name__
661 663
664
662 665 #----------------------------------------------------------------------------
663 666 class VerboseTB(TBTools):
664 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 671 traceback, to be used with alternate interpreters (because their own code
669 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 675 tb_offset=0, long_header=False, include_vars=True,
673 676 check_cache=None):
674 677 """Specify traceback offset, headers and color scheme.
@@ -691,126 +694,37 b' class VerboseTB(TBTools):'
691 694 check_cache = linecache.checkcache
692 695 self.check_cache = check_cache
693 696
694 def structured_traceback(self, etype, evalue, etb, tb_offset=None,
695 context=5):
696 """Return a nice text document describing the traceback."""
697
698 tb_offset = self.tb_offset if tb_offset is None else tb_offset
699
700 # some locals
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)) ) )
697 def format_records(self, records):
698 Colors = self.Colors # just a shorthand + quicker name lookup
699 ColorsNormal = Colors.Normal # used a lot
700 col_scheme = self.color_scheme_table.active_scheme_name
701 indent = ' ' * INDENT_SIZE
702 em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal)
703 undefined = '%sundefined%s' % (Colors.em, ColorsNormal)
765 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 705 # build some color string templates outside these nested loops
792 tpl_link = '%s%%s%s' % (Colors.filenameEm,ColorsNormal)
793 tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm,
794 ColorsNormal)
795 tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \
796 (Colors.vName, Colors.valEm, ColorsNormal)
797 tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal)
706 tpl_link = '%s%%s%s' % (Colors.filenameEm, ColorsNormal)
707 tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm,
708 ColorsNormal)
709 tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \
710 (Colors.vName, Colors.valEm, ColorsNormal)
711 tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal)
798 712 tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal,
799 713 Colors.vName, ColorsNormal)
800 tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal)
801 tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal)
802 tpl_line_em = '%s%%s%s %%s%s' % (Colors.linenoEm,Colors.line,
803 ColorsNormal)
714 tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal)
715
716 tpl_line = '%s%%s%s %%s' % (Colors.lineno, 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 720 abspath = os.path.abspath
807 721 for frame, file, lnum, func, lines, index in records:
808 722 #print '*** record:',file,lnum,func,lines,index # dbg
809 723 if not file:
810 724 file = '?'
811 elif not(file.startswith(str("<")) and file.endswith(str(">"))):
725 elif not (file.startswith(str("<")) and file.endswith(str(">"))):
812 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 728 try:
815 729 file = abspath(file)
816 730 except OSError:
@@ -827,9 +741,9 b' class VerboseTB(TBTools):'
827 741 # Decide whether to include variable details or not
828 742 var_repr = self.include_vars and eqrepr or nullrepr
829 743 try:
830 call = tpl_call % (func,inspect.formatargvalues(args,
831 varargs, varkw,
832 locals,formatvalue=var_repr))
744 call = tpl_call % (func, inspect.formatargvalues(args,
745 varargs, varkw,
746 locals, formatvalue=var_repr))
833 747 except KeyError:
834 748 # This happens in situations like errors inside generator
835 749 # expressions, where local variables are listed in the
@@ -848,12 +762,12 b' class VerboseTB(TBTools):'
848 762 # will illustrate the error, if this exception catch is
849 763 # disabled.
850 764 call = tpl_call_fail % func
851
765
852 766 # Don't attempt to tokenize binary files.
853 767 if file.endswith(('.so', '.pyd', '.dll')):
854 frames.append('%s %s\n' % (link,call))
768 frames.append('%s %s\n' % (link, call))
855 769 continue
856 elif file.endswith(('.pyc','.pyo')):
770 elif file.endswith(('.pyc', '.pyo')):
857 771 # Look up the corresponding source file.
858 772 file = openpy.source_from_cache(file)
859 773
@@ -867,7 +781,7 b' class VerboseTB(TBTools):'
867 781 try:
868 782 names = []
869 783 name_cont = False
870
784
871 785 for token_type, token, start, end, line in generate_tokens(linereader):
872 786 # build composite names
873 787 if token_type == tokenize.NAME and token not in keyword.kwlist:
@@ -890,9 +804,11 b' class VerboseTB(TBTools):'
890 804 name_cont = True
891 805 elif token_type == tokenize.NEWLINE:
892 806 break
893
894 except (IndexError, UnicodeDecodeError):
807
808 except (IndexError, UnicodeDecodeError, SyntaxError):
895 809 # signals exit of tokenizer
810 # SyntaxError can occur if the file is not actually Python
811 # - see gh-6300
896 812 pass
897 813 except tokenize.TokenError as msg:
898 814 _m = ("An unexpected error occurred while tokenizing input\n"
@@ -909,11 +825,11 b' class VerboseTB(TBTools):'
909 825 lvals = []
910 826 if self.include_vars:
911 827 for name_full in unique_names:
912 name_base = name_full.split('.',1)[0]
828 name_base = name_full.split('.', 1)[0]
913 829 if name_base in frame.f_code.co_varnames:
914 830 if name_base in locals:
915 831 try:
916 value = repr(eval(name_full,locals))
832 value = repr(eval(name_full, locals))
917 833 except:
918 834 value = undefined
919 835 else:
@@ -922,69 +838,191 b' class VerboseTB(TBTools):'
922 838 else:
923 839 if name_base in frame.f_globals:
924 840 try:
925 value = repr(eval(name_full,frame.f_globals))
841 value = repr(eval(name_full, frame.f_globals))
926 842 except:
927 843 value = undefined
928 844 else:
929 845 value = undefined
930 846 name = tpl_global_var % name_full
931 lvals.append(tpl_name_val % (name,value))
847 lvals.append(tpl_name_val % (name, value))
932 848 if lvals:
933 lvals = '%s%s' % (indent,em_normal.join(lvals))
849 lvals = '%s%s' % (indent, em_normal.join(lvals))
934 850 else:
935 851 lvals = ''
936 852
937 level = '%s %s\n' % (link,call)
853 level = '%s %s\n' % (link, call)
938 854
939 855 if index is None:
940 856 frames.append(level)
941 857 else:
942 frames.append('%s%s' % (level,''.join(
943 _format_traceback_lines(lnum,index,lines,Colors,lvals,
858 frames.append('%s%s' % (level, ''.join(
859 _format_traceback_lines(lnum, index, lines, Colors, lvals,
944 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 899 # Get (safely) a string form of the exception info
947 900 try:
948 etype_str,evalue_str = map(str,(etype,evalue))
901 etype_str, evalue_str = map(str, (etype, evalue))
949 902 except:
950 903 # User exception is improperly defined.
951 etype,evalue = str,sys.exc_info()[:2]
952 etype_str,evalue_str = map(str,(etype,evalue))
904 etype, evalue = str, sys.exc_info()[:2]
905 etype_str, evalue_str = map(str, (etype, evalue))
953 906 # ... and format it
954 exception = ['%s%s%s: %s' % (Colors.excName, etype_str,
955 ColorsNormal, py3compat.cast_unicode(evalue_str))]
907 exception = ['%s%s%s: %s' % (colors.excName, etype_str,
908 colorsnormal, py3compat.cast_unicode(evalue_str))]
909
956 910 if (not py3compat.PY3) and type(evalue) is types.InstanceType:
957 911 try:
958 912 names = [w for w in dir(evalue) if isinstance(w, py3compat.string_types)]
959 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 915 # when dir() is called on it. We do the best we can to report
962 916 # the problem and continue
963 917 _m = '%sException reporting error (object with broken dir())%s:'
964 exception.append(_m % (Colors.excName,ColorsNormal))
965 etype_str,evalue_str = map(str,sys.exc_info()[:2])
966 exception.append('%s%s%s: %s' % (Colors.excName,etype_str,
967 ColorsNormal, py3compat.cast_unicode(evalue_str)))
918 exception.append(_m % (colors.excName, colorsnormal))
919 etype_str, evalue_str = map(str, sys.exc_info()[:2])
920 exception.append('%s%s%s: %s' % (colors.excName, etype_str,
921 colorsnormal, py3compat.cast_unicode(evalue_str)))
968 922 names = []
969 923 for name in names:
970 924 value = text_repr(getattr(evalue, name))
971 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 945 if records:
975 filepath, lnum = records[-1][1:3]
976 #print "file:", str(file), "linenb", str(lnum) # dbg
977 filepath = os.path.abspath(filepath)
978 ipinst = get_ipython()
979 if ipinst is not None:
980 ipinst.hooks.synchronize_with_editor(filepath, lnum, 0)
981 # vds: <<
982
983 # return all our info assembled as a single string
984 # return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) )
985 return [head] + frames + [''.join(exception[0])]
986
987 def debugger(self,force=False):
946 filepath, lnum = records[-1][1:3]
947 filepath = os.path.abspath(filepath)
948 ipinst = get_ipython()
949 if ipinst is not None:
950 ipinst.hooks.synchronize_with_editor(filepath, lnum, 0)
951
952 return [[head] + frames + [''.join(formatted_exception[0])]]
953
954 def get_records(self, etb, number_of_lines_of_context, tb_offset):
955 try:
956 # Try the default getinnerframes and Alex's: Alex's fixes some
957 # problems, but it generates empty tracebacks for console errors
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 1026 """Call up the pdb debugger if desired, always clean up the tb
989 1027 reference.
990 1028
@@ -1014,7 +1052,7 b' class VerboseTB(TBTools):'
1014 1052 with display_trap:
1015 1053 self.pdb.reset()
1016 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 1056 etb = self.tb
1019 1057 else:
1020 1058 etb = self.tb = sys.last_traceback
@@ -1025,7 +1063,7 b' class VerboseTB(TBTools):'
1025 1063 self.pdb.botframe = etb.tb_frame
1026 1064 self.pdb.interaction(self.tb.tb_frame, self.tb)
1027 1065
1028 if hasattr(self,'tb'):
1066 if hasattr(self, 'tb'):
1029 1067 del self.tb
1030 1068
1031 1069 def handler(self, info=None):
@@ -1050,6 +1088,7 b' class VerboseTB(TBTools):'
1050 1088 except KeyboardInterrupt:
1051 1089 print("\nKeyboardInterrupt")
1052 1090
1091
1053 1092 #----------------------------------------------------------------------------
1054 1093 class FormattedTB(VerboseTB, ListTB):
1055 1094 """Subclass ListTB but allow calling with a traceback.
@@ -1069,7 +1108,7 b' class FormattedTB(VerboseTB, ListTB):'
1069 1108 check_cache=None):
1070 1109
1071 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 1112 self.verbose_modes = self.valid_modes[1:3]
1074 1113
1075 1114 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
@@ -1083,19 +1122,19 b' class FormattedTB(VerboseTB, ListTB):'
1083 1122 # set_mode also sets the tb_join_char attribute
1084 1123 self.set_mode(mode)
1085 1124
1086 def _extract_tb(self,tb):
1125 def _extract_tb(self, tb):
1087 1126 if tb:
1088 1127 return traceback.extract_tb(tb)
1089 1128 else:
1090 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 1132 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1094 1133 mode = self.mode
1095 1134 if mode in self.verbose_modes:
1096 1135 # Verbose modes need a full traceback
1097 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 1139 else:
1101 1140 # We must check the source cache because otherwise we can print
@@ -1104,7 +1143,7 b' class FormattedTB(VerboseTB, ListTB):'
1104 1143 # Now we can extract and format the exception
1105 1144 elist = self._extract_tb(tb)
1106 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 1149 def stb2text(self, stb):
@@ -1112,18 +1151,18 b' class FormattedTB(VerboseTB, ListTB):'
1112 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 1155 """Switch to the desired mode.
1117 1156
1118 1157 If mode is not specified, cycles through the available modes."""
1119 1158
1120 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 1161 len(self.valid_modes)
1123 1162 self.mode = self.valid_modes[new_idx]
1124 1163 elif mode not in self.valid_modes:
1125 raise ValueError('Unrecognized mode in FormattedTB: <'+mode+'>\n'
1126 'Valid modes: '+str(self.valid_modes))
1164 raise ValueError('Unrecognized mode in FormattedTB: <' + mode + '>\n'
1165 'Valid modes: ' + str(self.valid_modes))
1127 1166 else:
1128 1167 self.mode = mode
1129 1168 # include variable details only in 'Verbose' mode
@@ -1131,7 +1170,7 b' class FormattedTB(VerboseTB, ListTB):'
1131 1170 # Set the join character for generating text tracebacks
1132 1171 self.tb_join_char = self._join_chars[self.mode]
1133 1172
1134 # some convenient shorcuts
1173 # some convenient shortcuts
1135 1174 def plain(self):
1136 1175 self.set_mode(self.valid_modes[0])
1137 1176
@@ -1141,6 +1180,7 b' class FormattedTB(VerboseTB, ListTB):'
1141 1180 def verbose(self):
1142 1181 self.set_mode(self.valid_modes[2])
1143 1182
1183
1144 1184 #----------------------------------------------------------------------------
1145 1185 class AutoFormattedTB(FormattedTB):
1146 1186 """A traceback printer which can be called on the fly.
@@ -1156,8 +1196,8 b' class AutoFormattedTB(FormattedTB):'
1156 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,
1160 out=None,tb_offset=None):
1199 def __call__(self, etype=None, evalue=None, etb=None,
1200 out=None, tb_offset=None):
1161 1201 """Print out a formatted exception traceback.
1162 1202
1163 1203 Optional arguments:
@@ -1167,7 +1207,6 b' class AutoFormattedTB(FormattedTB):'
1167 1207 per-call basis (this overrides temporarily the instance's tb_offset
1168 1208 given at initialization time. """
1169 1209
1170
1171 1210 if out is None:
1172 1211 out = self.ostream
1173 1212 out.flush()
@@ -1182,33 +1221,36 b' class AutoFormattedTB(FormattedTB):'
1182 1221 print("\nKeyboardInterrupt")
1183 1222
1184 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 1225 if etype is None:
1187 etype,value,tb = sys.exc_info()
1226 etype, value, tb = sys.exc_info()
1188 1227 self.tb = tb
1189 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 1234 # A simple class to preserve Nathan's original functionality.
1195 1235 class ColorTB(FormattedTB):
1196 1236 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1197 def __init__(self,color_scheme='Linux',call_pdb=0):
1198 FormattedTB.__init__(self,color_scheme=color_scheme,
1237
1238 def __init__(self, color_scheme='Linux', call_pdb=0):
1239 FormattedTB.__init__(self, color_scheme=color_scheme,
1199 1240 call_pdb=call_pdb)
1200 1241
1201 1242
1202 1243 class SyntaxTB(ListTB):
1203 1244 """Extension which holds some state: the last exception value"""
1204 1245
1205 def __init__(self,color_scheme = 'NoColor'):
1206 ListTB.__init__(self,color_scheme)
1246 def __init__(self, color_scheme='NoColor'):
1247 ListTB.__init__(self, color_scheme)
1207 1248 self.last_syntax_error = None
1208 1249
1209 1250 def __call__(self, etype, value, elist):
1210 1251 self.last_syntax_error = value
1211 ListTB.__call__(self,etype,value,elist)
1252
1253 ListTB.__call__(self, etype, value, elist)
1212 1254
1213 1255 def structured_traceback(self, etype, value, elist, tb_offset=None,
1214 1256 context=5):
@@ -1223,7 +1265,7 b' class SyntaxTB(ListTB):'
1223 1265 if newtext:
1224 1266 value.text = newtext
1225 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 1270 def clear_err_state(self):
1229 1271 """Return the current error state and clear it"""
@@ -1236,7 +1278,46 b' class SyntaxTB(ListTB):'
1236 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 1321 # module testing (minimal)
1241 1322 if __name__ == "__main__":
1242 1323 def spam(c, d_e):
@@ -136,7 +136,7 b' def extract_zip(fd, dest):'
136 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 140 """Download and/or install MathJax for offline use.
141 141
142 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 150 Whether to remove and replace an existing install.
151 151 dest : str [IPYTHONDIR/nbextensions/mathjax]
152 152 Where to install mathjax
153 tag : str ['v2.2']
154 Which tag to download. Default is 'v2.2', the current stable release,
153 tag : str ['2.4.0']
154 Which tag to download. Default is '2.4.0', the current stable release,
155 155 but alternatives include 'v1.1a' and 'master'.
156 156 file : file like object [ defualt to content of https://github.com/mathjax/MathJax/tarball/#{tag}]
157 157 File handle from which to untar/unzip/... mathjax
@@ -80,6 +80,7 b' try:'
80 80 import traceback
81 81 import signal
82 82 import codecs
83 import stat
83 84 except ImportError: # pragma: no cover
84 85 err = sys.exc_info()[1]
85 86 raise ImportError(str(err) + '''
@@ -87,7 +88,7 b' except ImportError: # pragma: no cover'
87 88 A critical module was not found. Probably this operating system does not
88 89 support it. Pexpect is intended for UNIX-like operating systems.''')
89 90
90 __version__ = '3.2'
91 __version__ = '3.3'
91 92 __revision__ = ''
92 93 __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
93 94 'which', 'split_command_line', '__version__', '__revision__']
@@ -284,6 +285,7 b' class spawn(object):'
284 285 def _chr(c):
285 286 return bytes([c])
286 287 linesep = os.linesep.encode('ascii')
288 crlf = '\r\n'.encode('ascii')
287 289
288 290 @staticmethod
289 291 def write_to_stdout(b):
@@ -296,13 +298,14 b' class spawn(object):'
296 298 allowed_string_types = (basestring,) # analysis:ignore
297 299 _chr = staticmethod(chr)
298 300 linesep = os.linesep
301 crlf = '\r\n'
299 302 write_to_stdout = sys.stdout.write
300 303
301 304 encoding = None
302 305
303 306 def __init__(self, command, args=[], timeout=30, maxread=2000,
304 307 searchwindowsize=None, logfile=None, cwd=None, env=None,
305 ignore_sighup=True):
308 ignore_sighup=True, echo=True):
306 309
307 310 '''This is the constructor. The command parameter may be a string that
308 311 includes a command and any arguments to the command. For example::
@@ -415,7 +418,16 b' class spawn(object):'
415 418 signalstatus will store the signal value and exitstatus will be None.
416 419 If you need more detail you can also read the self.status member which
417 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 432 self.STDIN_FILENO = pty.STDIN_FILENO
421 433 self.STDOUT_FILENO = pty.STDOUT_FILENO
@@ -437,7 +449,7 b' class spawn(object):'
437 449 self.status = None
438 450 self.flag_eof = False
439 451 self.pid = None
440 # the chile filedescriptor is initially closed
452 # the child file descriptor is initially closed
441 453 self.child_fd = -1
442 454 self.timeout = timeout
443 455 self.delimiter = EOF
@@ -466,16 +478,30 b' class spawn(object):'
466 478 self.closed = True
467 479 self.cwd = cwd
468 480 self.env = env
481 self.echo = echo
469 482 self.ignore_sighup = ignore_sighup
483 _platform = sys.platform.lower()
470 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 486 # Solaris uses internal __fork_pty(). All others use pty.fork().
473 if ((sys.platform.lower().find('solaris') >= 0)
474 or (sys.platform.lower().find('sunos5') >= 0)):
475 self.use_native_pty_fork = False
476 else:
477 self.use_native_pty_fork = True
478
487 self.use_native_pty_fork = not (
488 _platform.startswith('solaris') or
489 _platform.startswith('sunos'))
490 # inherit EOF and INTR definitions from controlling process.
491 try:
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 505 # Support subclasses that do not use command or args.
480 506 if command is None:
481 507 self.command = None
@@ -599,33 +625,39 b' class spawn(object):'
599 625 if self.use_native_pty_fork:
600 626 try:
601 627 self.pid, self.child_fd = pty.fork()
602 except OSError:
628 except OSError: # pragma: no cover
603 629 err = sys.exc_info()[1]
604 630 raise ExceptionPexpect('pty.fork() failed: ' + str(err))
605 631 else:
606 632 # Use internal __fork_pty
607 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 640 # Child
641 self.child_fd = self.STDIN_FILENO
642
643 # set default window size of 24 rows by 80 columns
611 644 try:
612 # used by setwinsize()
613 self.child_fd = sys.stdout.fileno()
614 645 self.setwinsize(24, 80)
615 # which exception, shouldnt' we catch explicitly .. ?
616 except:
617 # Some platforms do not like setwinsize (Cygwin).
618 # This will cause problem when running applications that
619 # are very picky about window size.
620 # This is a serious limitation, but not a show stopper.
621 pass
646 except IOError as err:
647 if err.args[0] not in (errno.EINVAL, errno.ENOTTY):
648 raise
649
650 # disable echo if spawn argument echo was unset
651 if not self.echo:
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 658 # Do not allow child to inherit open file descriptors from parent.
623 659 max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
624 for i in range(3, max_fd):
625 try:
626 os.close(i)
627 except OSError:
628 pass
660 os.closerange(3, max_fd)
629 661
630 662 if self.ignore_sighup:
631 663 signal.signal(signal.SIGHUP, signal.SIG_IGN)
@@ -638,6 +670,13 b' class spawn(object):'
638 670 os.execvpe(self.command, self.args, self.env)
639 671
640 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 680 self.terminated = False
642 681 self.closed = False
643 682
@@ -660,19 +699,15 b' class spawn(object):'
660 699 raise ExceptionPexpect("Could not open with os.openpty().")
661 700
662 701 pid = os.fork()
663 if pid < 0:
664 raise ExceptionPexpect("Failed os.fork().")
665 elif pid == 0:
702 if pid == pty.CHILD:
666 703 # Child.
667 704 os.close(parent_fd)
668 705 self.__pty_make_controlling_tty(child_fd)
669 706
670 os.dup2(child_fd, 0)
671 os.dup2(child_fd, 1)
672 os.dup2(child_fd, 2)
707 os.dup2(child_fd, self.STDIN_FILENO)
708 os.dup2(child_fd, self.STDOUT_FILENO)
709 os.dup2(child_fd, self.STDERR_FILENO)
673 710
674 if child_fd > 2:
675 os.close(child_fd)
676 711 else:
677 712 # Parent.
678 713 os.close(child_fd)
@@ -686,44 +721,36 b' class spawn(object):'
686 721
687 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 727 try:
691 728 fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
692 if fd >= 0:
693 os.close(fd)
694 # which exception, shouldnt' we catch explicitly .. ?
695 except:
696 # Already disconnected. This happens if running inside cron.
697 pass
729 os.close(fd)
730 except OSError as err:
731 if err.errno != errno.ENXIO:
732 raise
698 733
699 734 os.setsid()
700 735
701 # Verify we are disconnected from controlling tty
702 # by attempting to open it again.
736 # Verify we are disconnected from controlling tty by attempting to open
737 # it again. We expect that OSError of ENXIO should always be raised.
703 738 try:
704 739 fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
705 if fd >= 0:
706 os.close(fd)
707 raise ExceptionPexpect('Failed to disconnect from ' +
708 'controlling tty. It is still possible to open /dev/tty.')
709 # which exception, shouldnt' we catch explicitly .. ?
710 except:
711 # Good! We are disconnected from a controlling tty.
712 pass
740 os.close(fd)
741 raise ExceptionPexpect("OSError of errno.ENXIO should be raised.")
742 except OSError as err:
743 if err.errno != errno.ENXIO:
744 raise
713 745
714 746 # Verify we can open child pty.
715 747 fd = os.open(child_name, os.O_RDWR)
716 if fd < 0:
717 raise ExceptionPexpect("Could not open child pty, " + child_name)
718 else:
719 os.close(fd)
748 os.close(fd)
720 749
721 750 # Verify we now have a controlling tty.
722 751 fd = os.open("/dev/tty", os.O_WRONLY)
723 if fd < 0:
724 raise ExceptionPexpect("Could not open controlling tty, /dev/tty")
725 else:
726 os.close(fd)
752 os.close(fd)
753
727 754
728 755 def fileno(self):
729 756 '''This returns the file descriptor of the pty for the child.
@@ -757,7 +784,12 b' class spawn(object):'
757 784
758 785 def isatty(self):
759 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 794 return os.isatty(self.child_fd)
763 795
@@ -794,12 +826,20 b' class spawn(object):'
794 826 def getecho(self):
795 827 '''This returns the terminal echo mode. This returns True if echo is
796 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)
800 if attr[3] & termios.ECHO:
801 return True
802 return False
831 Not supported on platforms where ``isatty()`` returns False. '''
832
833 try:
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 844 def setecho(self, state):
805 845 '''This sets the terminal echo mode on or off. Note that anything the
@@ -829,18 +869,35 b' class spawn(object):'
829 869 p.expect(['1234'])
830 870 p.expect(['abcd'])
831 871 p.expect(['wxyz'])
872
873
874 Not supported on platforms where ``isatty()`` returns False.
832 875 '''
833 876
834 self.child_fd
835 attr = termios.tcgetattr(self.child_fd)
877 errmsg = 'setecho() may not be called on this platform'
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 886 if state:
837 887 attr[3] = attr[3] | termios.ECHO
838 888 else:
839 889 attr[3] = attr[3] & ~termios.ECHO
840 # I tried TCSADRAIN and TCSAFLUSH, but
841 # these were inconsistent and blocked on some platforms.
842 # TCSADRAIN would probably be ideal if it worked.
843 termios.tcsetattr(self.child_fd, termios.TCSANOW, attr)
890
891 try:
892 # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and
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 902 def _log(self, s, direction):
846 903 if self.logfile is not None:
@@ -913,12 +970,14 b' class spawn(object):'
913 970 if self.child_fd in r:
914 971 try:
915 972 s = os.read(self.child_fd, size)
916 except OSError:
917 # Linux does this
918 self.flag_eof = True
919 raise EOF('End Of File (EOF). Exception style platform.')
973 except OSError as err:
974 if err.args[0] == errno.EIO:
975 # Linux-style EOF
976 self.flag_eof = True
977 raise EOF('End Of File (EOF). Exception style platform.')
978 raise
920 979 if s == b'':
921 # BSD style
980 # BSD-style EOF
922 981 self.flag_eof = True
923 982 raise EOF('End Of File (EOF). Empty string style platform.')
924 983
@@ -926,7 +985,7 b' class spawn(object):'
926 985 self._log(s, 'read')
927 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 990 def read(self, size=-1):
932 991 '''This reads at most "size" bytes from the file (less if the read hits
@@ -972,9 +1031,9 b' class spawn(object):'
972 1031 if size == 0:
973 1032 return self.string_type()
974 1033 # delimiter default is EOF
975 index = self.expect([b'\r\n', self.delimiter])
1034 index = self.expect([self.crlf, self.delimiter])
976 1035 if index == 0:
977 return self.before + b'\r\n'
1036 return self.before + self.crlf
978 1037 else:
979 1038 return self.before
980 1039
@@ -1075,40 +1134,14 b' class spawn(object):'
1075 1134 It is the responsibility of the caller to ensure the eof is sent at the
1076 1135 beginning of a line. '''
1077 1136
1078 ### Hmmm... how do I send an 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))
1137 self.send(self._chr(self._EOF))
1100 1138
1101 1139 def sendintr(self):
1102 1140
1103 1141 '''This sends a SIGINT to the child. It does not require
1104 1142 the SIGINT to be the first character on a line. '''
1105 1143
1106 if hasattr(termios, 'VINTR'):
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))
1144 self.send(self._chr(self._INTR))
1112 1145
1113 1146 def eof(self):
1114 1147
@@ -1181,7 +1214,7 b' class spawn(object):'
1181 1214 self.exitstatus = None
1182 1215 self.signalstatus = os.WTERMSIG(status)
1183 1216 self.terminated = True
1184 elif os.WIFSTOPPED(status):
1217 elif os.WIFSTOPPED(status): # pragma: no cover
1185 1218 # You can't call wait() on a child process in the stopped state.
1186 1219 raise ExceptionPexpect('Called wait() on a stopped child ' +
1187 1220 'process. This is not supported. Is some other ' +
@@ -1201,7 +1234,7 b' class spawn(object):'
1201 1234
1202 1235 if self.flag_eof:
1203 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 1238 # This is super-lame. The flag_eof would have been set
1206 1239 # in read_nonblocking(), so this should be safe.
1207 1240 waitpid_options = 0
@@ -1229,7 +1262,7 b' class spawn(object):'
1229 1262 try:
1230 1263 ### os.WNOHANG) # Solaris!
1231 1264 pid, status = os.waitpid(self.pid, waitpid_options)
1232 except OSError as e:
1265 except OSError as e: # pragma: no cover
1233 1266 # This should never happen...
1234 1267 if e.errno == errno.ECHILD:
1235 1268 raise ExceptionPexpect('isalive() encountered condition ' +
@@ -1643,10 +1676,14 b' class spawn(object):'
1643 1676 if self.child_fd in r:
1644 1677 try:
1645 1678 data = self.__interact_read(self.child_fd)
1646 except OSError as e:
1647 # The subprocess may have closed before we get to reading it
1648 if e.errno != errno.EIO:
1649 raise
1679 except OSError as err:
1680 if err.args[0] == errno.EIO:
1681 # Linux-style EOF
1682 break
1683 raise
1684 if data == b'':
1685 # BSD-style EOF
1686 break
1650 1687 if output_filter:
1651 1688 data = output_filter(data)
1652 1689 if self.logfile is not None:
@@ -1695,7 +1732,7 b' class spawn(object):'
1695 1732 ##############################################################################
1696 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 1737 '''This method is no longer supported or allowed. I don't like getters
1701 1738 and setters without a good reason. '''
@@ -1704,7 +1741,7 b' class spawn(object):'
1704 1741 'or allowed. Just assign a value to the ' +
1705 1742 'maxread member variable.')
1706 1743
1707 def setlog(self, fileobject):
1744 def setlog(self, fileobject): # pragma: no cover
1708 1745
1709 1746 '''This method is no longer supported or allowed.
1710 1747 '''
@@ -1732,11 +1769,13 b' class spawnu(spawn):'
1732 1769 allowed_string_types = (str, )
1733 1770 _chr = staticmethod(chr)
1734 1771 linesep = os.linesep
1772 crlf = '\r\n'
1735 1773 else:
1736 1774 string_type = unicode
1737 1775 allowed_string_types = (unicode, )
1738 1776 _chr = staticmethod(unichr)
1739 1777 linesep = os.linesep.decode('ascii')
1778 crlf = '\r\n'.decode('ascii')
1740 1779 # This can handle unicode in both Python 2 and 3
1741 1780 write_to_stdout = sys.stdout.write
1742 1781
@@ -1959,16 +1998,56 b' class searcher_re(object):'
1959 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 2044 '''This takes a given filename; tries to find it in the environment path;
1965 2045 then checks if it is executable. This returns the full path to the filename
1966 2046 if found and executable. Otherwise this returns None.'''
1967 2047
1968 2048 # Special case where filename contains an explicit path.
1969 if os.path.dirname(filename) != '':
1970 if os.access(filename, os.X_OK):
1971 return filename
2049 if os.path.dirname(filename) != '' and is_executable_file(filename):
2050 return filename
1972 2051 if 'PATH' not in os.environ or os.environ['PATH'] == '':
1973 2052 p = os.defpath
1974 2053 else:
@@ -1976,7 +2055,7 b' def which(filename):'
1976 2055 pathlist = p.split(os.pathsep)
1977 2056 for path in pathlist:
1978 2057 ff = os.path.join(path, filename)
1979 if os.access(ff, os.X_OK):
2058 if is_executable_file(ff):
1980 2059 return ff
1981 2060 return None
1982 2061
@@ -2041,4 +2120,4 b' def split_command_line(command_line):'
2041 2120 arg_list.append(arg)
2042 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 14 We are moving to a model where our JavaScript dependencies are managed using
15 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 18 `static/components/bower.json`. To update our bower packages, run `fab update`
18 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 21 ## less
26 22
27 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 29 ## JavaScript Documentation
31 30
@@ -1,21 +1,7 b''
1 """Base Tornado handlers for the notebook.
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 #-----------------------------------------------------------------------------
1 """Base Tornado handlers for the notebook server."""
18 2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
19 5
20 6 import functools
21 7 import json
@@ -41,7 +27,7 b' except ImportError:'
41 27 from IPython.config import Application
42 28 from IPython.utils.path import filefind
43 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 33 # Top-level handlers
@@ -53,6 +39,10 b' class AuthenticatedHandler(web.RequestHandler):'
53 39
54 40 def set_default_headers(self):
55 41 headers = self.settings.get('headers', {})
42
43 if "X-Frame-Options" not in headers:
44 headers["X-Frame-Options"] = "SAMEORIGIN"
45
56 46 for header_name,value in headers.items() :
57 47 try:
58 48 self.set_header(header_name, value)
@@ -137,6 +127,10 b' class IPythonHandler(AuthenticatedHandler):'
137 127 @property
138 128 def base_url(self):
139 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 136 # Manager objects
@@ -147,8 +141,8 b' class IPythonHandler(AuthenticatedHandler):'
147 141 return self.settings['kernel_manager']
148 142
149 143 @property
150 def notebook_manager(self):
151 return self.settings['notebook_manager']
144 def contents_manager(self):
145 return self.settings['contents_manager']
152 146
153 147 @property
154 148 def cluster_manager(self):
@@ -162,9 +156,47 b' class IPythonHandler(AuthenticatedHandler):'
162 156 def kernel_spec_manager(self):
163 157 return self.settings['kernel_spec_manager']
164 158
159 #---------------------------------------------------------------
160 # CORS
161 #---------------------------------------------------------------
162
165 163 @property
166 def project_dir(self):
167 return self.notebook_manager.notebook_dir
164 def allow_origin(self):
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 202 # template rendering
@@ -183,6 +215,7 b' class IPythonHandler(AuthenticatedHandler):'
183 215 def template_namespace(self):
184 216 return dict(
185 217 base_url=self.base_url,
218 ws_url=self.ws_url,
186 219 logged_in=self.logged_in,
187 220 login_available=self.login_available,
188 221 static_url=self.static_url,
@@ -202,12 +235,13 b' class IPythonHandler(AuthenticatedHandler):'
202 235 raise web.HTTPError(400, u'Invalid JSON in body of request')
203 236 return model
204 237
205 def get_error_html(self, status_code, **kwargs):
238 def write_error(self, status_code, **kwargs):
206 239 """render custom error pages"""
207 exception = kwargs.get('exception')
240 exc_info = kwargs.get('exc_info')
208 241 message = ''
209 242 status_message = responses.get(status_code, 'Unknown HTTP Error')
210 if exception:
243 if exc_info:
244 exception = exc_info[1]
211 245 # get the custom message, if defined
212 246 try:
213 247 message = exception.log_message % exception.args
@@ -227,13 +261,16 b' class IPythonHandler(AuthenticatedHandler):'
227 261 exception=exception,
228 262 )
229 263
264 self.set_header('Content-Type', 'text/html')
230 265 # render the template
231 266 try:
232 267 html = self.render_template('%s.html' % status_code, **ns)
233 268 except TemplateNotFound:
234 269 self.log.debug("No template for %d", status_code)
235 270 html = self.render_template('error.html', **ns)
236 return html
271
272 self.write(html)
273
237 274
238 275
239 276 class Template404(IPythonHandler):
@@ -372,6 +409,37 b' class TrailingSlashHandler(web.RequestHandler):'
372 409 def get(self):
373 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 444 # URL pattern fragments for re-use
377 445 #-----------------------------------------------------------------------------
@@ -379,6 +447,8 b' class TrailingSlashHandler(web.RequestHandler):'
379 447 path_regex = r"(?P<path>(?:/.*)*)"
380 448 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
381 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 454 # URL to handler mappings
@@ -15,6 +15,9 b' try:'
15 15 except ImportError:
16 16 from Cookie import SimpleCookie # Py 2
17 17 import logging
18
19 import tornado
20 from tornado import ioloop
18 21 from tornado import web
19 22 from tornado import websocket
20 23
@@ -26,29 +29,36 b' from .handlers import IPythonHandler'
26 29
27 30
28 31 class ZMQStreamHandler(websocket.WebSocketHandler):
29
30 def same_origin(self):
31 """Check to see that origin and host match in the headers."""
32
33 # The difference between version 8 and 13 is that in 8 the
34 # client sends a "Sec-Websocket-Origin" header and in 13 it's
35 # simply "Origin".
36 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"):
37 origin_header = self.request.headers.get("Sec-Websocket-Origin")
38 else:
39 origin_header = self.request.headers.get("Origin")
32
33 def check_origin(self, origin):
34 """Check Origin == Host or Access-Control-Allow-Origin.
35
36 Tornado >= 4 calls this method automatically, raising 403 if it returns False.
37 We call it explicitly in `open` on Tornado < 4.
38 """
39 if self.allow_origin == '*':
40 return True
40 41
41 42 host = self.request.headers.get("Host")
42 43
43 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 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 63 def clear_cookie(self, *args, **kwargs):
54 64 """meaningless for websockets"""
@@ -94,19 +104,41 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
94 104 """
95 105 return True
96 106
107 # ping interval for keeping websockets alive (30 seconds)
108 WS_PING_INTERVAL = 30000
97 109
98 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 120 def open(self, kernel_id):
101 121 self.kernel_id = cast_unicode(kernel_id, 'ascii')
102 122 # Check to see that origin matches host directly, including ports
103 if not self.same_origin():
104 self.log.warn("Cross Origin WebSocket Attempt.")
105 raise web.HTTPError(404)
123 # Tornado 4 already does CORS checking
124 if tornado.version_info[0] < 4:
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 129 self.session = Session(config=self.config)
108 130 self.save_on_message = self.on_message
109 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 143 def _inject_cookie_message(self, msg):
112 144 """Inject the first message, which is the document cookie,
@@ -8,31 +8,65 b' from subprocess import check_output'
8 8
9 9 pjoin = os.path.join
10 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 max_less_version = '1.5.0' # exclusive
14 min_less_version = '1.7.0'
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 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 53 for name in ('style', 'ipython'):
19 54 source = pjoin('style', "%s.less" % name)
20 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 59 def _to_bool(b):
24 60 if not b in ['True', 'False', True, False]:
25 61 abort('boolean expected, got: %s' % b)
26 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 65 """Compile a less file by source and target relative to static_dir"""
30 minify = _to_bool(minify)
31 verbose = _to_bool(verbose)
32 66 min_flag = '-x' if minify is True else ''
33 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 70 try:
37 71 out = check_output(['lessc', '--version'])
38 72 except OSError as err:
@@ -45,6 +79,7 b' def _compile_less(source, target, minify=True, verbose=False):'
45 79 if V(less_version) >= V(max_less_version):
46 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 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 6 import io
2 7 import os
3 8 import zipfile
4 9
5 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 16 from IPython.nbformat.current import to_notebook_json
9 17
10 18 from IPython.utils.py3compat import cast_bytes
@@ -73,7 +81,7 b' class NbconvertFileHandler(IPythonHandler):'
73 81 exporter = get_exporter(format, config=self.config, log=self.log)
74 82
75 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 86 self.set_header('Last-Modified', model['last_modified'])
79 87
@@ -123,6 +131,7 b' class NbconvertPostHandler(IPythonHandler):'
123 131
124 132 self.finish(output)
125 133
134
126 135 #-----------------------------------------------------------------------------
127 136 # URL to handler mappings
128 137 #-----------------------------------------------------------------------------
@@ -134,4 +143,5 b' default_handlers = ['
134 143 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
135 144 NbconvertFileHandler),
136 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 107 @onlyif_cmds_exist('pandoc')
108 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 110 nbmodel = requests.get(nbmodel_url).json()
111 111
112 112 r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
@@ -121,7 +121,7 b' class APITest(NotebookTestBase):'
121 121
122 122 @onlyif_cmds_exist('pandoc')
123 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 125 nbmodel = requests.get(nbmodel_url).json()
126 126
127 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:
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 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
18 5
19 6 import os
20 7 from tornado import web
21 8 HTTPError = web.HTTPError
22 9
23 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
24 from ..utils import url_path_join, url_escape
25
26 #-----------------------------------------------------------------------------
27 # Handlers
28 #-----------------------------------------------------------------------------
10 from ..base.handlers import (
11 IPythonHandler, FilesRedirectHandler,
12 notebook_path_regex, path_regex,
13 )
14 from ..utils import url_escape
29 15
30 16
31 17 class NotebookHandler(IPythonHandler):
@@ -35,17 +21,16 b' class NotebookHandler(IPythonHandler):'
35 21 """get renders the notebook template if a name is given, or
36 22 redirects to the '/files/' handler if the name is not given."""
37 23 path = path.strip('/')
38 nbm = self.notebook_manager
24 cm = self.contents_manager
39 25 if name is None:
40 26 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
41 27
42 28 # a .ipynb filename was given
43 if not nbm.notebook_exists(name, path):
29 if not cm.file_exists(name, path):
44 30 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
45 31 name = url_escape(name)
46 32 path = url_escape(path)
47 33 self.write(self.render_template('notebook.html',
48 project=self.project_dir,
49 34 notebook_path=path,
50 35 notebook_name=name,
51 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 43 # URL to handler mappings
@@ -85,6 +46,6 b' class NotebookRedirectHandler(IPythonHandler):'
85 46
86 47 default_handlers = [
87 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 7 from __future__ import print_function
8 8
9 import base64
9 10 import errno
10 11 import io
11 12 import json
12 13 import logging
13 14 import os
14 15 import random
16 import re
15 17 import select
16 18 import signal
17 19 import socket
@@ -53,8 +55,8 b' from IPython.html import DEFAULT_STATIC_FILES_PATH'
53 55 from .base.handlers import Template404
54 56 from .log import log_request
55 57 from .services.kernels.kernelmanager import MappingKernelManager
56 from .services.notebooks.nbmanager import NotebookManager
57 from .services.notebooks.filenbmanager import FileNotebookManager
58 from .services.contents.manager import ContentsManager
59 from .services.contents.filemanager import FileContentsManager
58 60 from .services.clusters.clustermanager import ClusterManager
59 61 from .services.sessions.sessionmanager import SessionManager
60 62
@@ -72,6 +74,7 b' from IPython.kernel.zmq.session import default_secure, Session'
72 74 from IPython.nbformat.sign import NotebookNotary
73 75 from IPython.utils.importstring import import_item
74 76 from IPython.utils import submodule
77 from IPython.utils.process import check_pid
75 78 from IPython.utils.traitlets import (
76 79 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
77 80 DottedObjectName, TraitError,
@@ -118,19 +121,19 b' def load_handlers(name):'
118 121
119 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 125 cluster_manager, session_manager, kernel_spec_manager, log,
123 126 base_url, settings_overrides, jinja_env_options):
124 127
125 128 settings = self.init_settings(
126 ipython_app, kernel_manager, notebook_manager, cluster_manager,
129 ipython_app, kernel_manager, contents_manager, cluster_manager,
127 130 session_manager, kernel_spec_manager, log, base_url,
128 131 settings_overrides, jinja_env_options)
129 132 handlers = self.init_handlers(settings)
130 133
131 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 137 cluster_manager, session_manager, kernel_spec_manager,
135 138 log, base_url, settings_overrides,
136 139 jinja_env_options=None):
@@ -162,13 +165,14 b' class NotebookWebApplication(web.Application):'
162 165
163 166 # managers
164 167 kernel_manager=kernel_manager,
165 notebook_manager=notebook_manager,
168 contents_manager=contents_manager,
166 169 cluster_manager=cluster_manager,
167 170 session_manager=session_manager,
168 171 kernel_spec_manager=kernel_spec_manager,
169 172
170 173 # IPython stuff
171 174 nbextensions_path = ipython_app.nbextensions_path,
175 websocket_url=ipython_app.websocket_url,
172 176 mathjax_url=ipython_app.mathjax_url,
173 177 config=ipython_app.config,
174 178 jinja2_env=env,
@@ -189,18 +193,20 b' class NotebookWebApplication(web.Application):'
189 193 handlers.extend(load_handlers('nbconvert.handlers'))
190 194 handlers.extend(load_handlers('kernelspecs.handlers'))
191 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 197 handlers.extend(load_handlers('services.clusters.handlers'))
194 198 handlers.extend(load_handlers('services.sessions.handlers'))
195 199 handlers.extend(load_handlers('services.nbconvert.handlers'))
196 200 handlers.extend(load_handlers('services.kernelspecs.handlers'))
197 201 # FIXME: /files/ should be handled by the Contents service when it exists
198 nbm = settings['notebook_manager']
199 if hasattr(nbm, 'notebook_dir'):
200 handlers.extend([
201 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
202 cm = settings['contents_manager']
203 if hasattr(cm, 'root_dir'):
204 handlers.append(
205 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : cm.root_dir}),
206 )
207 handlers.append(
202 208 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
203 ])
209 )
204 210 # prepend base_url onto the patterns that we match
205 211 new_handlers = []
206 212 for handler in handlers:
@@ -260,9 +266,9 b" flags['no-mathjax']=("
260 266 )
261 267
262 268 # Add notebook manager flags
263 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
264 'Auto-save a .py script everytime the .ipynb notebook is saved',
265 'Do not auto-save .py scripts for every notebook'))
269 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
270 'DEPRECATED, IGNORED',
271 'DEPRECATED, IGNORED'))
266 272
267 273 aliases = dict(base_aliases)
268 274
@@ -298,7 +304,7 b' class NotebookApp(BaseIPythonApplication):'
298 304
299 305 classes = [
300 306 KernelManager, ProfileDir, Session, MappingKernelManager,
301 NotebookManager, FileNotebookManager, NotebookNotary,
307 ContentsManager, FileContentsManager, NotebookNotary,
302 308 ]
303 309 flags = Dict(flags)
304 310 aliases = Dict(aliases)
@@ -333,8 +339,34 b' class NotebookApp(BaseIPythonApplication):'
333 339 self.file_to_run = base
334 340 self.notebook_dir = path
335 341
336 # Network related information.
337
342 # Network related information
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 370 ip = Unicode('localhost', config=True,
339 371 help="The IP address the notebook server will listen on."
340 372 )
@@ -357,6 +389,14 b' class NotebookApp(BaseIPythonApplication):'
357 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 400 cookie_secret = Bytes(b'', config=True,
361 401 help="""The random bytes used to secure cookies.
362 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 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 431 password = Unicode(u'', config=True,
373 432 help="""Hashed password to use for web authentication.
@@ -456,6 +515,13 b' class NotebookApp(BaseIPythonApplication):'
456 515 def _nbextensions_path_default(self):
457 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 525 mathjax_url = Unicode("", config=True,
460 526 help="""The url for MathJax.js."""
461 527 )
@@ -482,13 +548,7 b' class NotebookApp(BaseIPythonApplication):'
482 548 return url
483 549
484 550 # no local mathjax, serve from CDN
485 if self.certfile:
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"
551 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
492 552 self.log.info("Using MathJax from CDN: %s", url)
493 553 return url
494 554
@@ -499,7 +559,7 b' class NotebookApp(BaseIPythonApplication):'
499 559 else:
500 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 563 config=True,
504 564 help='The notebook manager class to use.'
505 565 )
@@ -563,7 +623,7 b' class NotebookApp(BaseIPythonApplication):'
563 623 raise TraitError("No such notebook dir: %r" % new)
564 624
565 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 627 self.config.MappingKernelManager.root_dir = new
568 628
569 629
@@ -589,11 +649,8 b' class NotebookApp(BaseIPythonApplication):'
589 649
590 650 def init_kernel_argv(self):
591 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 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 655 def init_configurables(self):
599 656 # force Session default to be secure
@@ -603,10 +660,12 b' class NotebookApp(BaseIPythonApplication):'
603 660 parent=self, log=self.log, kernel_argv=self.kernel_argv,
604 661 connection_dir = self.profile_dir.security_dir,
605 662 )
606 kls = import_item(self.notebook_manager_class)
607 self.notebook_manager = kls(parent=self, log=self.log)
663 kls = import_item(self.contents_manager_class)
664 self.contents_manager = kls(parent=self, log=self.log)
608 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 669 kls = import_item(self.cluster_manager_class)
611 670 self.cluster_manager = kls(parent=self, log=self.log)
612 671 self.cluster_manager.update_profiles()
@@ -625,8 +684,13 b' class NotebookApp(BaseIPythonApplication):'
625 684
626 685 def init_webapp(self):
627 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 692 self.web_app = NotebookWebApplication(
629 self, self.kernel_manager, self.notebook_manager,
693 self, self.kernel_manager, self.contents_manager,
630 694 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
631 695 self.log, self.base_url, self.webapp_settings,
632 696 self.jinja_environment_options
@@ -717,8 +781,6 b' class NotebookApp(BaseIPythonApplication):'
717 781
718 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 784 info = self.log.info
723 785 info('interrupted')
724 786 print(self.notebook_info())
@@ -778,7 +840,7 b' class NotebookApp(BaseIPythonApplication):'
778 840
779 841 def notebook_info(self):
780 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 844 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
783 845 return info + "The IPython Notebook is running at: %s" % self.display_url
784 846
@@ -790,6 +852,7 b' class NotebookApp(BaseIPythonApplication):'
790 852 'secure': bool(self.certfile),
791 853 'base_url': self.base_url,
792 854 'notebook_dir': os.path.abspath(self.notebook_dir),
855 'pid': os.getpid()
793 856 }
794 857
795 858 def write_server_info_file(self):
@@ -863,8 +926,17 b" def list_running_servers(profile='default'):"
863 926 for file in os.listdir(pd.security_dir):
864 927 if file.startswith('nbserver-'):
865 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 941 # Main entry point
870 942 #-----------------------------------------------------------------------------
@@ -21,7 +21,6 b' from zmq.eventloop import ioloop'
21 21
22 22 from IPython.config.configurable import LoggingConfigurable
23 23 from IPython.utils.traitlets import Dict, Instance, CFloat
24 from IPython.parallel.apps.ipclusterapp import IPClusterStart
25 24 from IPython.core.profileapp import list_profiles_in
26 25 from IPython.core.profiledir import ProfileDir
27 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 37 class ClusterManager(LoggingConfigurable):
@@ -59,6 +47,20 b' class ClusterManager(LoggingConfigurable):'
59 47 return IOLoop.instance()
60 48
61 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 64 starter = DummyIPClusterStart(log=self.log)
63 65 starter.initialize(['--profile-dir', profile_dir])
64 66 cl = starter.controller_launcher
1 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
@@ -1,6 +1,7 b''
1 1 # coding: utf-8
2 """Test the notebooks webservice API."""
2 """Test the contents webservice API."""
3 3
4 import base64
4 5 import io
5 6 import json
6 7 import os
@@ -21,23 +22,21 b' from IPython.utils import py3compat'
21 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 # no longer listed by the notebook web service.
26 def notebooks_only(nb_list):
27 return [nb for nb in nb_list if nb['type']=='notebook']
25 def notebooks_only(dir_model):
26 return [nb for nb in dir_model['content'] if nb['type']=='notebook']
28 27
29 def dirs_only(nb_list):
30 return [x for x in nb_list if x['type']=='directory']
28 def dirs_only(dir_model):
29 return [x for x in dir_model['content'] if x['type']=='directory']
31 30
32 31
33 class NBAPI(object):
34 """Wrapper for notebook API calls."""
32 class API(object):
33 """Wrapper for contents API calls."""
35 34 def __init__(self, base_url):
36 35 self.base_url = base_url
37 36
38 37 def _req(self, verb, path, body=None):
39 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 40 data=body,
42 41 )
43 42 response.raise_for_status()
@@ -49,8 +48,11 b' class NBAPI(object):'
49 48 def read(self, name, path='/'):
50 49 return self._req('GET', url_path_join(path, name))
51 50
52 def create_untitled(self, path='/'):
53 return self._req('POST', path)
51 def create_untitled(self, path='/', ext=None):
52 body = None
53 if ext:
54 body = json.dumps({'ext': ext})
55 return self._req('POST', path, body)
54 56
55 57 def upload_untitled(self, body, path='/'):
56 58 return self._req('POST', path, body)
@@ -65,6 +67,9 b' class NBAPI(object):'
65 67 def upload(self, name, body, path='/'):
66 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 73 def copy(self, copy_from, copy_to, path='/'):
69 74 body = json.dumps({'copy_from':copy_from})
70 75 return self._req('PUT', url_path_join(path, copy_to), body)
@@ -112,8 +117,20 b' class APITest(NotebookTestBase):'
112 117 del dirs[0] # remove ''
113 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 128 def setUp(self):
116 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 135 for d in (self.dirs + self.hidden_dirs):
119 136 d.replace('/', os.sep)
@@ -122,12 +139,22 b' class APITest(NotebookTestBase):'
122 139
123 140 for d, name in self.dirs_nbs:
124 141 d = d.replace('/', os.sep)
142 # create a notebook
125 143 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
126 144 encoding='utf-8') as f:
127 145 nb = new_notebook(name=name)
128 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 159 def tearDown(self):
133 160 nbdir = self.notebook_dir.name
@@ -139,175 +166,287 b' class APITest(NotebookTestBase):'
139 166 os.unlink(pjoin(nbdir, 'inroot.ipynb'))
140 167
141 168 def test_list_notebooks(self):
142 nbs = notebooks_only(self.nb_api.list().json())
169 nbs = notebooks_only(self.api.list().json())
143 170 self.assertEqual(len(nbs), 1)
144 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 174 self.assertEqual(len(nbs), 1)
148 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 178 self.assertEqual(len(nbs), 1)
152 179 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
153 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 183 self.assertEqual(len(nbs), 1)
157 184 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
158 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 188 self.assertEqual(len(nbs), 4)
162 189 nbnames = { normalize('NFC', n['name']) for n in nbs }
163 190 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb']
164 191 expected = { normalize('NFC', name) for name in expected }
165 192 self.assertEqual(nbnames, expected)
166
167 nbs = notebooks_only(self.nb_api.list('ordering').json())
193
194 nbs = notebooks_only(self.api.list('ordering').json())
168 195 nbnames = [n['name'] for n in nbs]
169 196 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
170 197 self.assertEqual(nbnames, expected)
171 198
172 199 def test_list_dirs(self):
173 dirs = dirs_only(self.nb_api.list().json())
200 dirs = dirs_only(self.api.list().json())
174 201 dir_names = {normalize('NFC', d['name']) for d in dirs}
175 202 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
176 203
177 204 def test_list_nonexistant_dir(self):
178 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 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 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 215 self.assertIn('content', nb)
186 216 self.assertIn('metadata', nb['content'])
187 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 247 # Name that doesn't exist - should be a 404
190 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 252 self.assertEqual(resp.status_code, 201)
195 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)))
197 self.assertEqual(resp.json()['name'], name)
198 assert os.path.isfile(pjoin(
254 self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path, name)))
255 rjson = resp.json()
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 261 self.notebook_dir.name,
200 262 path.replace('/', os.sep),
201 263 name,
202 264 ))
203 265
204 266 def test_create_untitled(self):
205 resp = self.nb_api.create_untitled(path=u'å b')
206 self._check_nb_created(resp, 'Untitled0.ipynb', u'å b')
267 resp = self.api.create_untitled(path=u'å b')
268 self._check_created(resp, 'Untitled0.ipynb', u'å b')
207 269
208 270 # Second time
209 resp = self.nb_api.create_untitled(path=u'å b')
210 self._check_nb_created(resp, 'Untitled1.ipynb', u'å b')
271 resp = self.api.create_untitled(path=u'å b')
272 self._check_created(resp, 'Untitled1.ipynb', u'å b')
211 273
212 274 # And two directories down
213 resp = self.nb_api.create_untitled(path='foo/bar')
214 self._check_nb_created(resp, 'Untitled0.ipynb', 'foo/bar')
275 resp = self.api.create_untitled(path='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 288 def test_upload_untitled(self):
217 289 nb = new_notebook(name='Upload test')
218 nbmodel = {'content': nb}
219 resp = self.nb_api.upload_untitled(path=u'å b',
290 nbmodel = {'content': nb, 'type': 'notebook'}
291 resp = self.api.upload_untitled(path=u'å b',
220 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 295 def test_upload(self):
224 296 nb = new_notebook(name=u'ignored')
225 nbmodel = {'content': nb}
226 resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
297 nbmodel = {'content': nb, 'type': 'notebook'}
298 resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
227 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 346 def test_upload_v2(self):
231 347 nb = v2.new_notebook()
232 348 ws = v2.new_worksheet()
233 349 nb.worksheets.append(ws)
234 350 ws.cells.append(v2.new_code_cell(input='print("hi")'))
235 nbmodel = {'content': nb}
236 resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
351 nbmodel = {'content': nb, 'type': 'notebook'}
352 resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
237 353 body=json.dumps(nbmodel))
238 self._check_nb_created(resp, u'Upload tést.ipynb', u'å b')
239 resp = self.nb_api.read(u'Upload tést.ipynb', u'å b')
354 self._check_created(resp, u'Upload tést.ipynb', u'å b')
355 resp = self.api.read(u'Upload tést.ipynb', u'å b')
240 356 data = resp.json()
241 357 self.assertEqual(data['content']['nbformat'], current.nbformat)
242 358 self.assertEqual(data['content']['orig_nbformat'], 2)
243 359
244 360 def test_copy_untitled(self):
245 resp = self.nb_api.copy_untitled(u'ç d.ipynb', path=u'å b')
246 self._check_nb_created(resp, u'ç d-Copy0.ipynb', u'å b')
361 resp = self.api.copy_untitled(u'ç d.ipynb', path=u'å b')
362 self._check_created(resp, u'ç d-Copy0.ipynb', u'å b')
247 363
248 364 def test_copy(self):
249 resp = self.nb_api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
250 self._check_nb_created(resp, u'cøpy.ipynb', u'å b')
365 resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=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 377 def test_delete(self):
253 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 380 self.assertEqual(resp.status_code, 204)
256 381
257 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 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 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 402 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
264 403 self.assertEqual(resp.json()['name'], 'z.ipynb')
265 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 407 nbnames = set(n['name'] for n in nbs)
269 408 self.assertIn('z.ipynb', nbnames)
270 409 self.assertNotIn('a.ipynb', nbnames)
271 410
272 411 def test_rename_existing(self):
273 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 415 def test_save(self):
277 resp = self.nb_api.read('a.ipynb', 'foo')
416 resp = self.api.read('a.ipynb', 'foo')
278 417 nbcontent = json.loads(resp.text)['content']
279 418 nb = to_notebook_json(nbcontent)
280 419 ws = new_worksheet()
281 420 nb.worksheets = [ws]
282 421 ws.cells.append(new_heading_cell(u'Created by test ³'))
283 422
284 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
285 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
423 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
424 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
286 425
287 426 nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
288 427 with io.open(nbfile, 'r', encoding='utf-8') as f:
289 428 newnb = read(f, format='ipynb')
290 429 self.assertEqual(newnb.worksheets[0].cells[0].source,
291 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 432 newnb = to_notebook_json(nbcontent)
294 433 self.assertEqual(newnb.worksheets[0].cells[0].source,
295 434 u'Created by test ³')
296 435
297 436 # Save and rename
298 nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb}
299 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
437 nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb, 'type': 'notebook'}
438 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
300 439 saved = resp.json()
301 440 self.assertEqual(saved['name'], 'a2.ipynb')
302 441 self.assertEqual(saved['path'], 'foo/bar')
303 442 assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
304 443 assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
305 444 with assert_http_error(404):
306 self.nb_api.read('a.ipynb', 'foo')
445 self.api.read('a.ipynb', 'foo')
307 446
308 447 def test_checkpoints(self):
309 resp = self.nb_api.read('a.ipynb', 'foo')
310 r = self.nb_api.new_checkpoint('a.ipynb', 'foo')
448 resp = self.api.read('a.ipynb', 'foo')
449 r = self.api.new_checkpoint('a.ipynb', 'foo')
311 450 self.assertEqual(r.status_code, 201)
312 451 cp1 = r.json()
313 452 self.assertEqual(set(cp1), {'id', 'last_modified'})
@@ -321,27 +460,26 b' class APITest(NotebookTestBase):'
321 460 hcell = new_heading_cell('Created by test')
322 461 ws.cells.append(hcell)
323 462 # Save
324 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
325 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
463 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
464 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
326 465
327 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 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 471 nb = to_notebook_json(nbcontent)
333 472 self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
334 473
335 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 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 478 nb = to_notebook_json(nbcontent)
340 479 self.assertEqual(nb.worksheets, [])
341 480
342 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 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 485 self.assertEqual(cps, [])
347
@@ -15,74 +15,74 b' from IPython.utils.tempdir import TemporaryDirectory'
15 15 from IPython.utils.traitlets import TraitError
16 16 from IPython.html.utils import url_path_join
17 17
18 from ..filenbmanager import FileNotebookManager
19 from ..nbmanager import NotebookManager
18 from ..filemanager import FileContentsManager
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 25 with TemporaryDirectory() as td:
26 fm = FileNotebookManager(notebook_dir=td)
27 self.assertEqual(fm.notebook_dir, td)
26 fm = FileContentsManager(root_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 30 with TemporaryDirectory() as td:
31 nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
32 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir)
31 root = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
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 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 38 def test_get_os_path(self):
39 39 # full filesystem path should be returned with correct operating system
40 40 # separators.
41 41 with TemporaryDirectory() as td:
42 nbdir = td
43 fm = FileNotebookManager(notebook_dir=nbdir)
42 root = td
43 fm = FileContentsManager(root_dir=root)
44 44 path = fm._get_os_path('test.ipynb', '/path/to/notebook/')
45 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 47 self.assertEqual(path, fs_path)
48 48
49 fm = FileNotebookManager(notebook_dir=nbdir)
49 fm = FileContentsManager(root_dir=root)
50 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 52 self.assertEqual(path, fs_path)
53 53
54 fm = FileNotebookManager(notebook_dir=nbdir)
54 fm = FileContentsManager(root_dir=root)
55 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 57 self.assertEqual(path, fs_path)
58
58
59 59 def test_checkpoint_subdir(self):
60 60 subd = u'sub ∂ir'
61 61 cp_name = 'test-cp.ipynb'
62 62 with TemporaryDirectory() as td:
63 nbdir = td
63 root = td
64 64 os.mkdir(os.path.join(td, subd))
65 fm = FileNotebookManager(notebook_dir=nbdir)
65 fm = FileContentsManager(root_dir=root)
66 66 cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/')
67 67 cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd)
68 68 self.assertNotEqual(cp_dir, cp_subdir)
69 self.assertEqual(cp_dir, os.path.join(nbdir, fm.checkpoint_dir, cp_name))
70 self.assertEqual(cp_subdir, os.path.join(nbdir, subd, fm.checkpoint_dir, cp_name))
71
69 self.assertEqual(cp_dir, os.path.join(root, fm.checkpoint_dir, cp_name))
70 self.assertEqual(cp_subdir, os.path.join(root, subd, fm.checkpoint_dir, cp_name))
71
72
73 class TestContentsManager(TestCase):
72 74
73 class TestNotebookManager(TestCase):
74
75 75 def setUp(self):
76 76 self._temp_dir = TemporaryDirectory()
77 77 self.td = self._temp_dir.name
78 self.notebook_manager = FileNotebookManager(
79 notebook_dir=self.td,
78 self.contents_manager = FileContentsManager(
79 root_dir=self.td,
80 80 log=logging.getLogger()
81 81 )
82
82
83 83 def tearDown(self):
84 84 self._temp_dir.cleanup()
85
85
86 86 def make_dir(self, abs_path, rel_path):
87 87 """make subdirectory, rel_path is the relative path
88 88 to that directory from the location where the server started"""
@@ -91,31 +91,31 b' class TestNotebookManager(TestCase):'
91 91 os.makedirs(os_path)
92 92 except OSError:
93 93 print("Directory already exists: %r" % os_path)
94
94
95 95 def add_code_cell(self, nb):
96 96 output = current.new_output("display_data", output_javascript="alert('hi');")
97 97 cell = current.new_code_cell("print('hi')", outputs=[output])
98 98 if not nb.worksheets:
99 99 nb.worksheets.append(current.new_worksheet())
100 100 nb.worksheets[0].cells.append(cell)
101
101
102 102 def new_notebook(self):
103 nbm = self.notebook_manager
104 model = nbm.create_notebook()
103 cm = self.contents_manager
104 model = cm.create_file()
105 105 name = model['name']
106 106 path = model['path']
107
108 full_model = nbm.get_notebook(name, path)
107
108 full_model = cm.get_model(name, path)
109 109 nb = full_model['content']
110 110 self.add_code_cell(nb)
111
112 nbm.save_notebook(full_model, name, path)
111
112 cm.save(full_model, name, path)
113 113 return nb, name, path
114
115 def test_create_notebook(self):
116 nm = self.notebook_manager
114
115 def test_create_file(self):
116 cm = self.contents_manager
117 117 # Test in root directory
118 model = nm.create_notebook()
118 model = cm.create_file()
119 119 assert isinstance(model, dict)
120 120 self.assertIn('name', model)
121 121 self.assertIn('path', model)
@@ -124,23 +124,23 b' class TestNotebookManager(TestCase):'
124 124
125 125 # Test in sub-directory
126 126 sub_dir = '/foo/'
127 self.make_dir(nm.notebook_dir, 'foo')
128 model = nm.create_notebook(None, sub_dir)
127 self.make_dir(cm.root_dir, 'foo')
128 model = cm.create_file(None, sub_dir)
129 129 assert isinstance(model, dict)
130 130 self.assertIn('name', model)
131 131 self.assertIn('path', model)
132 132 self.assertEqual(model['name'], 'Untitled0.ipynb')
133 133 self.assertEqual(model['path'], sub_dir.strip('/'))
134 134
135 def test_get_notebook(self):
136 nm = self.notebook_manager
135 def test_get(self):
136 cm = self.contents_manager
137 137 # Create a notebook
138 model = nm.create_notebook()
138 model = cm.create_file()
139 139 name = model['name']
140 140 path = model['path']
141 141
142 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 144 assert isinstance(model2, dict)
145 145 self.assertIn('name', model2)
146 146 self.assertIn('path', model2)
@@ -149,66 +149,66 b' class TestNotebookManager(TestCase):'
149 149
150 150 # Test in sub-directory
151 151 sub_dir = '/foo/'
152 self.make_dir(nm.notebook_dir, 'foo')
153 model = nm.create_notebook(None, sub_dir)
154 model2 = nm.get_notebook(name, sub_dir)
152 self.make_dir(cm.root_dir, 'foo')
153 model = cm.create_file(None, sub_dir)
154 model2 = cm.get_model(name, sub_dir)
155 155 assert isinstance(model2, dict)
156 156 self.assertIn('name', model2)
157 157 self.assertIn('path', model2)
158 158 self.assertIn('content', model2)
159 159 self.assertEqual(model2['name'], 'Untitled0.ipynb')
160 160 self.assertEqual(model2['path'], sub_dir.strip('/'))
161
162 def test_update_notebook(self):
163 nm = self.notebook_manager
161
162 def test_update(self):
163 cm = self.contents_manager
164 164 # Create a notebook
165 model = nm.create_notebook()
165 model = cm.create_file()
166 166 name = model['name']
167 167 path = model['path']
168 168
169 169 # Change the name in the model for rename
170 170 model['name'] = 'test.ipynb'
171 model = nm.update_notebook(model, name, path)
171 model = cm.update(model, name, path)
172 172 assert isinstance(model, dict)
173 173 self.assertIn('name', model)
174 174 self.assertIn('path', model)
175 175 self.assertEqual(model['name'], 'test.ipynb')
176 176
177 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 180 # Test in sub-directory
181 181 # Create a directory and notebook in that directory
182 182 sub_dir = '/foo/'
183 self.make_dir(nm.notebook_dir, 'foo')
184 model = nm.create_notebook(None, sub_dir)
183 self.make_dir(cm.root_dir, 'foo')
184 model = cm.create_file(None, sub_dir)
185 185 name = model['name']
186 186 path = model['path']
187
187
188 188 # Change the name in the model for rename
189 189 model['name'] = 'test_in_sub.ipynb'
190 model = nm.update_notebook(model, name, path)
190 model = cm.update(model, name, path)
191 191 assert isinstance(model, dict)
192 192 self.assertIn('name', model)
193 193 self.assertIn('path', model)
194 194 self.assertEqual(model['name'], 'test_in_sub.ipynb')
195 195 self.assertEqual(model['path'], sub_dir.strip('/'))
196
196
197 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):
201 nm = self.notebook_manager
200 def test_save(self):
201 cm = self.contents_manager
202 202 # Create a notebook
203 model = nm.create_notebook()
203 model = cm.create_file()
204 204 name = model['name']
205 205 path = model['path']
206 206
207 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 210 # Save the notebook
211 model = nm.save_notebook(full_model, name, path)
211 model = cm.save(full_model, name, path)
212 212 assert isinstance(model, dict)
213 213 self.assertIn('name', model)
214 214 self.assertIn('path', model)
@@ -218,103 +218,84 b' class TestNotebookManager(TestCase):'
218 218 # Test in sub-directory
219 219 # Create a directory and notebook in that directory
220 220 sub_dir = '/foo/'
221 self.make_dir(nm.notebook_dir, 'foo')
222 model = nm.create_notebook(None, sub_dir)
221 self.make_dir(cm.root_dir, 'foo')
222 model = cm.create_file(None, sub_dir)
223 223 name = model['name']
224 224 path = model['path']
225 model = nm.get_notebook(name, path)
225 model = cm.get_model(name, path)
226 226
227 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 229 assert isinstance(model, dict)
230 230 self.assertIn('name', model)
231 231 self.assertIn('path', model)
232 232 self.assertEqual(model['name'], 'Untitled0.ipynb')
233 233 self.assertEqual(model['path'], sub_dir.strip('/'))
234 234
235 def test_save_notebook_with_script(self):
236 nm = self.notebook_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
235 def test_delete(self):
236 cm = self.contents_manager
256 237 # Create a notebook
257 238 nb, name, path = self.new_notebook()
258
239
259 240 # Delete the notebook
260 nm.delete_notebook(name, path)
261
241 cm.delete(name, path)
242
262 243 # Check that a 'get' on the deleted notebook raises and error
263 self.assertRaises(HTTPError, nm.get_notebook, name, path)
264
265 def test_copy_notebook(self):
266 nm = self.notebook_manager
244 self.assertRaises(HTTPError, cm.get_model, name, path)
245
246 def test_copy(self):
247 cm = self.contents_manager
267 248 path = u'å b'
268 249 name = u'nb √.ipynb'
269 os.mkdir(os.path.join(nm.notebook_dir, path))
270 orig = nm.create_notebook({'name' : name}, path=path)
271
250 os.mkdir(os.path.join(cm.root_dir, path))
251 orig = cm.create_file({'name' : name}, path=path)
252
272 253 # copy with unspecified name
273 copy = nm.copy_notebook(name, path=path)
254 copy = cm.copy(name, path=path)
274 255 self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb'))
275
256
276 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 259 self.assertEqual(copy2['name'], u'copy 2.ipynb')
279
260
280 261 def test_trust_notebook(self):
281 nbm = self.notebook_manager
262 cm = self.contents_manager
282 263 nb, name, path = self.new_notebook()
283
284 untrusted = nbm.get_notebook(name, path)['content']
285 assert not nbm.notary.check_cells(untrusted)
286
264
265 untrusted = cm.get_model(name, path)['content']
266 assert not cm.notary.check_cells(untrusted)
267
287 268 # print(untrusted)
288 nbm.trust_notebook(name, path)
289 trusted = nbm.get_notebook(name, path)['content']
269 cm.trust_notebook(name, path)
270 trusted = cm.get_model(name, path)['content']
290 271 # print(trusted)
291 assert nbm.notary.check_cells(trusted)
292
272 assert cm.notary.check_cells(trusted)
273
293 274 def test_mark_trusted_cells(self):
294 nbm = self.notebook_manager
275 cm = self.contents_manager
295 276 nb, name, path = self.new_notebook()
296
297 nbm.mark_trusted_cells(nb, name, path)
277
278 cm.mark_trusted_cells(nb, name, path)
298 279 for cell in nb.worksheets[0].cells:
299 280 if cell.cell_type == 'code':
300 281 assert not cell.trusted
301
302 nbm.trust_notebook(name, path)
303 nb = nbm.get_notebook(name, path)['content']
282
283 cm.trust_notebook(name, path)
284 nb = cm.get_model(name, path)['content']
304 285 for cell in nb.worksheets[0].cells:
305 286 if cell.cell_type == 'code':
306 287 assert cell.trusted
307 288
308 289 def test_check_and_sign(self):
309 nbm = self.notebook_manager
290 cm = self.contents_manager
310 291 nb, name, path = self.new_notebook()
311
312 nbm.mark_trusted_cells(nb, name, path)
313 nbm.check_and_sign(nb, name, path)
314 assert not nbm.notary.check_signature(nb)
315
316 nbm.trust_notebook(name, path)
317 nb = nbm.get_notebook(name, path)['content']
318 nbm.mark_trusted_cells(nb, name, path)
319 nbm.check_and_sign(nb, name, path)
320 assert nbm.notary.check_signature(nb)
292
293 cm.mark_trusted_cells(nb, name, path)
294 cm.check_and_sign(nb, name, path)
295 assert not cm.notary.check_signature(nb)
296
297 cm.trust_notebook(name, path)
298 nb = cm.get_model(name, path)['content']
299 cm.mark_trusted_cells(nb, name, path)
300 cm.check_and_sign(nb, name, path)
301 assert cm.notary.check_signature(nb)
@@ -27,8 +27,16 b' class MainKernelHandler(IPythonHandler):'
27 27 @web.authenticated
28 28 @json_errors
29 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 38 km = self.kernel_manager
31 kernel_id = km.start_kernel()
39 kernel_id = km.start_kernel(kernel_name=name)
32 40 model = km.kernel_model(kernel_id)
33 41 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
34 42 self.set_header('Location', url_escape(location))
@@ -76,6 +84,9 b' class KernelActionHandler(IPythonHandler):'
76 84
77 85 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
78 86
87 def __repr__(self):
88 return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized'))
89
79 90 def create_stream(self):
80 91 km = self.kernel_manager
81 92 meth = getattr(km, 'connect_%s' % self.channel)
@@ -137,6 +148,12 b' class ZMQChannelHandler(AuthenticatedZMQStreamHandler):'
137 148 self.zmq_stream.on_recv(self._on_zmq_reply)
138 149
139 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 157 msg = json.loads(msg)
141 158 self.session.send(self.zmq_stream, msg)
142 159
@@ -72,8 +72,8 b' class MappingKernelManager(MultiKernelManager):'
72 72 os_path = os.path.dirname(os_path)
73 73 return os_path
74 74
75 def start_kernel(self, kernel_id=None, path=None, **kwargs):
76 """Start a kernel for a session an return its kernel_id.
75 def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
76 """Start a kernel for a session and return its kernel_id.
77 77
78 78 Parameters
79 79 ----------
@@ -84,12 +84,16 b' class MappingKernelManager(MultiKernelManager):'
84 84 path : API path
85 85 The API path (unicode, '/' delimited) for the cwd.
86 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 91 if kernel_id is None:
89 92 kwargs['extra_arguments'] = self.kernel_argv
90 93 if path is not None:
91 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 97 self.log.info("Kernel started: %s" % kernel_id)
94 98 self.log.debug("Kernel args: %r" % kwargs)
95 99 # register callback for failed auto-restart
@@ -111,7 +115,8 b' class MappingKernelManager(MultiKernelManager):'
111 115 """Return a dictionary of kernel information described in the
112 116 JSON standard model."""
113 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 120 return model
116 121
117 122 def list_kernels(self):
@@ -1,6 +1,6 b''
1 1 """Test the kernels service API."""
2 2
3
3 import json
4 4 import requests
5 5
6 6 from IPython.html.utils import url_path_join
@@ -30,8 +30,9 b' class KernelAPI(object):'
30 30 def get(self, id):
31 31 return self._req('GET', id)
32 32
33 def start(self):
34 return self._req('POST', '')
33 def start(self, name='python'):
34 body = json.dumps({'name': name})
35 return self._req('POST', '', body)
35 36
36 37 def shutdown(self, id):
37 38 return self._req('DELETE', id)
@@ -64,11 +65,14 b' class KernelAPITest(NotebookTestBase):'
64 65 self.assertEqual(r.status_code, 201)
65 66 self.assertIsInstance(kern1, dict)
66 67
68 self.assertEqual(r.headers['x-frame-options'], "SAMEORIGIN")
69
67 70 # GET request
68 71 r = self.kern_api.list()
69 72 self.assertEqual(r.status_code, 200)
70 73 assert isinstance(r.json(), list)
71 74 self.assertEqual(r.json()[0]['id'], kern1['id'])
75 self.assertEqual(r.json()[0]['name'], kern1['name'])
72 76
73 77 # create another kernel and check that they both are added to the
74 78 # list of kernels from a GET request
@@ -89,6 +93,7 b' class KernelAPITest(NotebookTestBase):'
89 93 self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id'])
90 94 rekern = r.json()
91 95 self.assertEqual(rekern['id'], kern2['id'])
96 self.assertEqual(rekern['name'], kern2['name'])
92 97
93 98 def test_kernel_handler(self):
94 99 # GET kernel with given id
@@ -7,6 +7,8 b' from tornado import web'
7 7
8 8 from ...base.handlers import IPythonHandler, json_errors
9 9
10 from IPython.kernel.kernelspec import _pythonfirst
11
10 12
11 13 class MainKernelSpecHandler(IPythonHandler):
12 14 SUPPORTED_METHODS = ('GET',)
@@ -16,7 +18,7 b' class MainKernelSpecHandler(IPythonHandler):'
16 18 def get(self):
17 19 ksm = self.kernel_spec_manager
18 20 results = []
19 for kernel_name in ksm.find_kernel_specs():
21 for kernel_name in sorted(ksm.find_kernel_specs(), key=_pythonfirst):
20 22 d = ksm.get_kernel_spec(kernel_name).to_dict()
21 23 d['name'] = kernel_name
22 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:
4
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 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
18 5
19 6 import json
20 7
@@ -24,10 +11,6 b' from ...base.handlers import IPythonHandler, json_errors'
24 11 from IPython.utils.jsonutil import date_default
25 12 from IPython.html.utils import url_path_join, url_escape
26 13
27 #-----------------------------------------------------------------------------
28 # Session web service handlers
29 #-----------------------------------------------------------------------------
30
31 14
32 15 class SessionRootHandler(IPythonHandler):
33 16
@@ -45,27 +28,30 b' class SessionRootHandler(IPythonHandler):'
45 28 # Creates a new session
46 29 #(unless a session already exists for the named nb)
47 30 sm = self.session_manager
48 nbm = self.notebook_manager
31 cm = self.contents_manager
49 32 km = self.kernel_manager
33
50 34 model = self.get_json_body()
51 35 if model is None:
52 36 raise web.HTTPError(400, "No JSON data provided")
53 37 try:
54 38 name = model['notebook']['name']
55 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 41 try:
58 42 path = model['notebook']['path']
59 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 50 # Check to see if session exists
62 51 if sm.session_exists(name=name, path=path):
63 52 model = sm.get_session(name=name, path=path)
64 53 else:
65 # allow nbm to specify kernels cwd
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)
54 model = sm.create_session(name=name, path=path, kernel_name=kernel_name)
69 55 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
70 56 self.set_header('Location', url_escape(location))
71 57 self.set_status(201)
@@ -108,10 +94,7 b' class SessionHandler(IPythonHandler):'
108 94 def delete(self, session_id):
109 95 # Deletes the session with given session_id
110 96 sm = self.session_manager
111 km = self.kernel_manager
112 session = sm.get_session(session_id=session_id)
113 97 sm.delete_session(session_id)
114 km.shutdown_kernel(session['kernel']['id'])
115 98 self.set_status(204)
116 99 self.finish()
117 100
@@ -23,12 +23,16 b' from tornado import web'
23 23
24 24 from IPython.config.configurable import LoggingConfigurable
25 25 from IPython.utils.py3compat import unicode_type
26 from IPython.utils.traitlets import Instance
26 27
27 28 #-----------------------------------------------------------------------------
28 29 # Classes
29 30 #-----------------------------------------------------------------------------
30 31
31 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 37 # Session database initialized below
34 38 _cursor = None
@@ -69,10 +73,15 b' class SessionManager(LoggingConfigurable):'
69 73 "Create a uuid for a new session"
70 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 77 """Creates a session and returns its model"""
74 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 86 def save_session(self, session_id, name=None, path=None, kernel_id=None):
78 87 """Saves the items for the session with the given session_id
@@ -170,8 +179,7 b' class SessionManager(LoggingConfigurable):'
170 179 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
171 180 self.cursor.execute(query, list(kwargs.values()) + [session_id])
172 181
173 @staticmethod
174 def row_factory(cursor, row):
182 def row_factory(self, cursor, row):
175 183 """Takes sqlite database session row and turns it into a dictionary"""
176 184 row = sqlite3.Row(cursor, row)
177 185 model = {
@@ -180,9 +188,7 b' class SessionManager(LoggingConfigurable):'
180 188 'name': row['name'],
181 189 'path': row['path']
182 190 },
183 'kernel': {
184 'id': row['kernel_id'],
185 }
191 'kernel': self.kernel_manager.kernel_model(row['kernel_id'])
186 192 }
187 193 return model
188 194
@@ -195,5 +201,6 b' class SessionManager(LoggingConfigurable):'
195 201 def delete_session(self, session_id):
196 202 """Deletes the row in the session database with given session_id"""
197 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 206 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
@@ -5,79 +5,101 b' from unittest import TestCase'
5 5 from tornado import web
6 6
7 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 31 class TestSessionManager(TestCase):
10 32
11 33 def test_get_session(self):
12 sm = SessionManager()
13 session_id = sm.new_session_id()
14 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
34 sm = SessionManager(kernel_manager=DummyMKM())
35 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
36 kernel_name='bar')['id']
15 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 41 self.assertEqual(model, expected)
18 42
19 43 def test_bad_get_session(self):
20 44 # Should raise error if a bad key is passed to the database.
21 sm = SessionManager()
22 session_id = sm.new_session_id()
23 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
45 sm = SessionManager(kernel_manager=DummyMKM())
46 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
47 kernel_name='foo')['id']
24 48 self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword
25 49
26 50 def test_list_sessions(self):
27 sm = SessionManager()
28 session_id1 = sm.new_session_id()
29 session_id2 = sm.new_session_id()
30 session_id3 = sm.new_session_id()
31 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678')
32 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678')
33 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678')
51 sm = SessionManager(kernel_manager=DummyMKM())
52 sessions = [
53 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
54 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
55 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
56 ]
34 57 sessions = sm.list_sessions()
35 expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
36 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}},
37 {'id':session_id2, 'notebook': {'name':u'test2.ipynb',
38 'path': u'/path/to/2/'}, 'kernel':{'id':u'5678'}},
39 {'id':session_id3, 'notebook':{'name':u'test3.ipynb',
40 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}]
58 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
59 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
60 {'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb',
61 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}},
62 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
63 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
41 64 self.assertEqual(sessions, expected)
42 65
43 66 def test_update_session(self):
44 sm = SessionManager()
45 session_id = sm.new_session_id()
46 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id=None)
47 sm.update_session(session_id, kernel_id='5678')
67 sm = SessionManager(kernel_manager=DummyMKM())
68 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
69 kernel_name='julia')['id']
48 70 sm.update_session(session_id, name='new_name.ipynb')
49 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 75 self.assertEqual(model, expected)
52 76
53 77 def test_bad_update_session(self):
54 78 # try to update a session with a bad keyword ~ raise error
55 sm = SessionManager()
56 session_id = sm.new_session_id()
57 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
79 sm = SessionManager(kernel_manager=DummyMKM())
80 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
81 kernel_name='ir')['id']
58 82 self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
59 83
60 84 def test_delete_session(self):
61 sm = SessionManager()
62 session_id1 = sm.new_session_id()
63 session_id2 = sm.new_session_id()
64 session_id3 = sm.new_session_id()
65 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678')
66 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678')
67 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678')
68 sm.delete_session(session_id2)
69 sessions = sm.list_sessions()
70 expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
71 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}},
72 {'id':session_id3, 'notebook':{'name':u'test3.ipynb',
73 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}]
74 self.assertEqual(sessions, expected)
85 sm = SessionManager(kernel_manager=DummyMKM())
86 sessions = [
87 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
88 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
89 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
90 ]
91 sm.delete_session(sessions[1]['id'])
92 new_sessions = sm.list_sessions()
93 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
94 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
95 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
96 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
97 self.assertEqual(new_sessions, expected)
75 98
76 99 def test_bad_delete_session(self):
77 100 # try to delete a session that doesn't exist ~ raise error
78 sm = SessionManager()
79 session_id = sm.new_session_id()
80 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
101 sm = SessionManager(kernel_manager=DummyMKM())
102 sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python')
81 103 self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
82 104 self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
83 105
@@ -37,8 +37,9 b' class SessionAPI(object):'
37 37 def get(self, id):
38 38 return self._req('GET', id)
39 39
40 def create(self, name, path):
41 body = json.dumps({'notebook': {'name':name, 'path':path}})
40 def create(self, name, path, kernel_name='python'):
41 body = json.dumps({'notebook': {'name':name, 'path':path},
42 'kernel': {'name': kernel_name}})
42 43 return self._req('POST', '', body)
43 44
44 45 def modify(self, id, name, path):
@@ -1,21 +1,12 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
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 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 // On document ready
10 //============================================================================
11
12
13 $(document).ready(function () {
14
15 IPython.page = new IPython.Page();
4 var ipython = ipython || {};
5 require(['base/js/page'], function(page) {
6 var page_instance = new page.Page();
16 7 $('button#login_submit').addClass("btn btn-default");
17 IPython.page.show();
8 page_instance.show();
18 9 $('input#password_input').focus();
19
10
11 ipython.page = page_instance;
20 12 });
21
@@ -1,43 +1,35 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
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 // Login button
10 //============================================================================
11
12 var IPython = (function (IPython) {
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
4 define([
5 'base/js/namespace',
6 'base/js/utils',
7 'jquery',
8 ], function(IPython, utils, $){
13 9 "use strict";
14 10
15 11 var LoginWidget = function (selector, options) {
16 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 14 this.selector = selector;
19 15 if (this.selector !== undefined) {
20 16 this.element = $(selector);
21 this.style();
22 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 23 LoginWidget.prototype.bind_events = function () {
32 24 var that = this;
33 25 this.element.find("button#logout").click(function () {
34 window.location = IPython.utils.url_join_encode(
26 window.location = utils.url_join_encode(
35 27 that.base_url,
36 28 "logout"
37 29 );
38 30 });
39 31 this.element.find("button#login").click(function () {
40 window.location = IPython.utils.url_join_encode(
32 window.location = utils.url_join_encode(
41 33 that.base_url,
42 34 "login"
43 35 );
@@ -47,6 +39,5 b' var IPython = (function (IPython) {'
47 39 // Set module variables
48 40 IPython.LoginWidget = LoginWidget;
49 41
50 return IPython;
51
52 }(IPython));
42 return {'LoginWidget': LoginWidget};
43 });
@@ -1,20 +1,10 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
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 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 // On document ready
10 //============================================================================
11
12
13 $(document).ready(function () {
14
15 IPython.page = new IPython.Page();
16 $('#ipython-main-app').addClass('border-box-sizing');
17 IPython.page.show();
4 var ipython = ipython || {};
5 require(['base/js/page'], function(page) {
6 var page_instance = new page.Page();
7 page_instance.show();
18 8
9 ipython.page = page_instance;
19 10 });
20
@@ -1,2 +1,7 b''
1 /*!
2 *
3 * IPython auth
4 *
5 */
1 6 @import "login.less";
2 7 @import "logout.less"; No newline at end of file
@@ -1,20 +1,14 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
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 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 // Utility for modal dialogs with bootstrap
10 //============================================================================
11
12 IPython.namespace('IPython.dialog');
13
14 IPython.dialog = (function (IPython) {
4 define([
5 'base/js/namespace',
6 'jquery',
7 ], function(IPython, $) {
15 8 "use strict";
16 9
17 10 var modal = function (options) {
11
18 12 var modal = $("<div/>")
19 13 .addClass("modal")
20 14 .addClass("fade")
@@ -79,26 +73,28 b' IPython.dialog = (function (IPython) {'
79 73 });
80 74 }
81 75 modal.on("hidden.bs.modal", function () {
82 if (IPython.notebook) {
83 var cell = IPython.notebook.get_selected_cell();
76 if (options.notebook) {
77 var cell = options.notebook.get_selected_cell();
84 78 if (cell) cell.select();
85 IPython.keyboard_manager.enable();
86 IPython.keyboard_manager.command_mode();
79 }
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) {
91 IPython.keyboard_manager.disable();
86 if (options.keyboard_manager) {
87 options.keyboard_manager.disable();
92 88 }
93 89
94 90 return modal.modal(options);
95 91 };
96 92
97 var edit_metadata = function (md, callback, name) {
98 name = name || "Cell";
93 var edit_metadata = function (options) {
94 options.name = options.name || "Cell";
99 95 var error_div = $('<div/>').css('color', 'red');
100 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 98 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
103 99 " so they don't conflict with those of others.";
104 100
@@ -106,7 +102,7 b' IPython.dialog = (function (IPython) {'
106 102 .attr('rows', '13')
107 103 .attr('cols', '80')
108 104 .attr('name', 'metadata')
109 .text(JSON.stringify(md || {}, null, 2));
105 .text(JSON.stringify(options.md || {}, null, 2));
110 106
111 107 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
112 108 .append(
@@ -128,8 +124,8 b' IPython.dialog = (function (IPython) {'
128 124 autoIndent: true,
129 125 mode: 'application/json',
130 126 });
131 var modal = IPython.dialog.modal({
132 title: "Edit " + name + " Metadata",
127 var modal_obj = modal({
128 title: "Edit " + options.name + " Metadata",
133 129 body: dialogform,
134 130 buttons: {
135 131 OK: { class : "btn-primary",
@@ -143,19 +139,25 b' IPython.dialog = (function (IPython) {'
143 139 error_div.text('WARNING: Could not save invalid JSON.');
144 140 return false;
145 141 }
146 callback(new_md);
142 options.callback(new_md);
147 143 }
148 144 },
149 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 155 modal : modal,
158 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 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
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 //============================================================================
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
11 3
12 4 // Give us an object to bind all events to. This object should be created
13 5 // before all other objects so it exists when others register event handlers.
14 // To trigger an event handler:
15 // $([IPython.events]).trigger('event.Namespace');
16 // To handle it:
17 // $([IPython.events]).on('event.Namespace',function () {});
6 // To register an event handler:
7 //
8 // require(['base/js/events'], function (events) {
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 13 "use strict";
21 14
22 var utils = IPython.utils;
23
24 15 var Events = function () {};
25
16
17 var events = new Events();
18
19 // Backwards compatability.
26 20 IPython.Events = Events;
27 IPython.events = new Events();
28
29 return IPython;
30
31 }(IPython));
32
21 IPython.events = events;
22
23 return $([events]);
24 });
@@ -1,19 +1,14 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 // Keyboard management
10 //============================================================================
11
12 IPython.namespace('IPython.keyboard');
13
14 IPython.keyboard = (function (IPython) {
4 define([
5 'base/js/namespace',
6 'jquery',
7 'base/js/utils',
8 ], function(IPython, $, utils) {
15 9 "use strict";
16 10
11
17 12 // Setup global keycodes and inverse keycodes.
18 13
19 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 46 '; :': 186, '= +': 187, '- _': 189
52 47 };
53 48
54 var browser = IPython.utils.browser[0];
55 var platform = IPython.utils.platform;
49 var browser = utils.browser[0];
50 var platform = utils.platform;
56 51
57 52 if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
58 53 $.extend(_keycodes, _mozilla_keycodes);
@@ -130,18 +125,19 b' IPython.keyboard = (function (IPython) {'
130 125
131 126 // Shortcut manager class
132 127
133 var ShortcutManager = function (delay) {
128 var ShortcutManager = function (delay, events) {
134 129 this._shortcuts = {};
135 130 this._counts = {};
136 131 this._timers = {};
137 132 this.delay = delay || 800; // delay in milliseconds
133 this.events = events;
138 134 };
139 135
140 136 ShortcutManager.prototype.help = function () {
141 137 var help = [];
142 138 for (var shortcut in this._shortcuts) {
143 var help_string = this._shortcuts[shortcut]['help'];
144 var help_index = this._shortcuts[shortcut]['help_index'];
139 var help_string = this._shortcuts[shortcut].help;
140 var help_index = this._shortcuts[shortcut].help_index;
145 141 if (help_string) {
146 142 if (platform === 'MacOS') {
147 143 shortcut = shortcut.replace('meta', 'cmd');
@@ -182,7 +178,7 b' IPython.keyboard = (function (IPython) {'
182 178 this._shortcuts[shortcut] = data;
183 179 if (!suppress_help_update) {
184 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 187 this.add_shortcut(shortcut, data[shortcut], true);
192 188 }
193 189 // update the keyboard shortcuts notebook help
194 $([IPython.events]).trigger('rebuild.QuickHelp');
190 this.events.trigger('rebuild.QuickHelp');
195 191 };
196 192
197 193 ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
@@ -200,7 +196,7 b' IPython.keyboard = (function (IPython) {'
200 196 delete this._shortcuts[shortcut];
201 197 if (!suppress_help_update) {
202 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 207 var timer = null;
212 208 if (c[shortcut] === data.count-1) {
213 209 c[shortcut] = 0;
214 var timer = t[shortcut];
210 timer = t[shortcut];
215 211 if (timer) {clearTimeout(timer); delete t[shortcut];}
216 212 return data.handler(event);
217 213 } else {
@@ -228,7 +224,7 b' IPython.keyboard = (function (IPython) {'
228 224 var shortcut = event_to_shortcut(event);
229 225 var data = this._shortcuts[shortcut];
230 226 if (data) {
231 var handler = data['handler'];
227 var handler = data.handler;
232 228 if (handler) {
233 229 if (data.count === 1) {
234 230 return handler(event);
@@ -243,10 +239,10 b' IPython.keyboard = (function (IPython) {'
243 239 ShortcutManager.prototype.handles = function (event) {
244 240 var shortcut = event_to_shortcut(event);
245 241 var data = this._shortcuts[shortcut];
246 return !( data === undefined || data.handler === undefined )
247 }
242 return !( data === undefined || data.handler === undefined );
243 };
248 244
249 return {
245 var keyboard = {
250 246 keycodes : keycodes,
251 247 inv_keycodes : inv_keycodes,
252 248 ShortcutManager : ShortcutManager,
@@ -256,4 +252,8 b' IPython.keyboard = (function (IPython) {'
256 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 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 4 var IPython = IPython || {};
9
10 IPython.version = "3.0.0-dev";
11
12 IPython.namespace = function (ns_string) {
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
5 define([], function(){
6 IPython.version = "3.0.0-dev";
7 return IPython;
8 });
@@ -1,32 +1,19 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
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 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 // Global header/site setup.
10 //============================================================================
11
12 var IPython = (function (IPython) {
4 define([
5 'base/js/namespace',
6 'jquery',
7 ], function(IPython, $){
13 8 "use strict";
14 9
15 10 var Page = function () {
16 this.style();
17 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 14 Page.prototype.bind_events = function () {
27 15 };
28 16
29
30 17 Page.prototype.show = function () {
31 18 // The header and site divs start out hidden to prevent FLOUC.
32 19 // Main scripts should call this method after styling everything.
@@ -34,23 +21,21 b' var IPython = (function (IPython) {'
34 21 this.show_site();
35 22 };
36 23
37
38 24 Page.prototype.show_header = function () {
39 25 // The header and site divs start out hidden to prevent FLOUC.
40 26 // Main scripts should call this method after styling everything.
27 // TODO: selector are hardcoded, pass as constructor argument
41 28 $('div#header').css('display','block');
42 29 };
43 30
44
45 31 Page.prototype.show_site = function () {
46 32 // The header and site divs start out hidden to prevent FLOUC.
47 33 // Main scripts should call this method after styling everything.
34 // TODO: selector are hardcoded, pass as constructor argument
48 35 $('div#site').css('display','block');
49 36 };
50 37
51
38 // Register self in the global namespace for convenience.
52 39 IPython.Page = Page;
53
54 return IPython;
55
56 }(IPython));
40 return {'Page': Page};
41 });
@@ -1,19 +1,12 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2014 The IPython Development Team
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 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 // Utilities
10 //============================================================================
11 IPython.namespace('IPython.security');
12
13 IPython.security = (function (IPython) {
4 define([
5 'base/js/namespace',
6 'jquery',
7 'components/google-caja/html-css-sanitizer-minified',
8 ], function(IPython, $) {
14 9 "use strict";
15
16 var utils = IPython.utils;
17 10
18 11 var noop = function (x) { return x; };
19 12
@@ -117,10 +110,12 b' IPython.security = (function (IPython) {'
117 110 return sanitized;
118 111 };
119 112
120 return {
113 var security = {
121 114 caja: caja,
122 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 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 //============================================================================
5 // Utilities
6 //============================================================================
7
8 IPython.namespace('IPython.utils');
9
10 IPython.utils = (function (IPython) {
4 define([
5 'base/js/namespace',
6 'jquery',
7 ], function(IPython, $){
11 8 "use strict";
12 9
13 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 526 var log_ajax_error = function (jqXHR, status, error) {
521 527 // log ajax failures with informative messages
522 528 var msg = "API request failed (" + jqXHR.status + "): ";
523 529 console.log(jqXHR);
524 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
525 msg += jqXHR.responseJSON.message;
526 } else {
527 msg += jqXHR.statusText;
528 }
530 msg += ajax_error_msg(jqXHR);
529 531 console.log(msg);
530 532 };
531
532 return {
533
534 var utils = {
533 535 regex_split : regex_split,
534 536 uuid : uuid,
535 537 fixConsole : fixConsole,
@@ -550,8 +552,12 b' IPython.utils = (function (IPython) {'
550 552 platform: platform,
551 553 is_or_has : is_or_has,
552 554 is_focused : is_focused,
555 ajax_error_msg : ajax_error_msg,
553 556 log_ajax_error : log_ajax_error,
554 557 };
555 558
556 }(IPython));
557
559 // Backwards compatability.
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 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 211 .hbox.align-start,
188 212 .vbox.align-start,
189 213 .align-start {
@@ -219,3 +243,27 b' Browsers not listed, including Safari, are supported via the styling under the'
219 243 /* Modern browsers */
220 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 24 padding-left: 30px;
25 25 padding-bottom: 5px;
26 26 border-bottom: 1px solid @navbar-default-border;
27 .border-box-sizing();
27 28 }
28 29
29 30 #ipython_notebook {
@@ -50,6 +51,7 b' div#header {'
50 51 #site {
51 52 width: 100%;
52 53 display: none;
54 .border-box-sizing();
53 55 }
54 56
55 57 /* Smaller buttons */
@@ -69,6 +71,14 b' span#login_widget {'
69 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 82 .nav-header {
73 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 6 @import "variables.less";
2 7 @import "mixins.less";
3 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 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 17 * Create a custom button in toolbar that execute `%qtconsole` in kernel
18 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 21 * IPython.toolbar.add_buttons_group([
22 22 * {
23 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 44 * Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );`
36 45 * to load custom script into the notebook.
@@ -122,4 +122,4 b' dateFormat.i18n = {'
122 122 // For convenience...
123 123 Date.prototype.format = function (mask, utc) {
124 124 return dateFormat(this, mask, utc);
125 };
125 }; No newline at end of file
@@ -1,53 +1,61 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
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 // Cell
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) {
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 // TODO: remove IPython dependency here
19 10 "use strict";
20 11
21 var utils = IPython.utils;
22 var keycodes = IPython.keyboard.keycodes;
12 // monkey patch CM to be able to syntax highlight cell magics
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 /**
25 * The Base `Cell` class from which to inherit
26 * @class Cell
27 **/
21 CodeMirror.patchedGetMode = function(config, mode){
22 var cmmode = CodeMirror.getMode(config, mode);
23 if(cmmode.indent === null) {
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 31 var Cell = function (options) {
36
37 options = this.mergeopt(Cell, options);
32 // Constructor
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 46 // superclass default overwrite our default
39 47
40 this.placeholder = options.placeholder || '';
41 this.read_only = options.cm_config.readOnly;
48 this.placeholder = config.placeholder || '';
49 this.read_only = config.cm_config.readOnly;
42 50 this.selected = false;
43 51 this.rendered = false;
44 52 this.mode = 'command';
45 53 this.metadata = {};
46 54 // load this from metadata later ?
47 55 this.user_highlight = 'auto';
48 this.cm_config = options.cm_config;
56 this.cm_config = config.cm_config;
49 57 this.cell_id = utils.uuid();
50 this._options = options;
58 this._options = config;
51 59
52 60 // For JS VM engines optimization, attributes should be all set (even
53 61 // to null) in the constructor, and if possible, if different subclass
@@ -70,7 +78,12 b' var IPython = (function (IPython) {'
70 78 cm_config : {
71 79 indentUnit : 4,
72 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 140 // We trigger events so that Cell doesn't have to depend on Notebook.
128 141 that.element.click(function (event) {
129 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 146 that.element.focusin(function (event) {
134 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 151 if (this.code_mirror) {
139 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 156 if (this.code_mirror) {
144 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 161 if (this.code_mirror) {
149 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 178 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
166 179 */
167 180 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
168 var that = this;
169 var shortcuts = IPython.keyboard_manager.edit_shortcuts;
181 var shortcuts = this.keyboard_manager.edit_shortcuts;
170 182
171 183 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
172 184 // manager will handle it
@@ -544,9 +556,8 b' var IPython = (function (IPython) {'
544 556 this.code_mirror.setOption('mode', default_mode);
545 557 };
546 558
559 // Backwards compatibility.
547 560 IPython.Cell = Cell;
548 561
549 return IPython;
550
551 }(IPython));
552
562 return {'Cell': Cell};
563 });
@@ -1,32 +1,28 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
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 // 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) {
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/events'
8 ], function(IPython, $, events) {
20 9 "use strict";
21 10
22 /**
23 * @constructor
24 * @class CellToolbar
25 * @param {The cell to attach the metadata UI to} cell
26 */
27 var CellToolbar = function (cell) {
11 var CellToolbar = function (options) {
12 // Constructor
13 //
14 // Parameters:
15 // options: dictionary
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 23 CellToolbar._instances.push(this);
29 this.cell = cell;
24 this.notebook = options.notebook;
25 this.cell = options.cell;
30 26 this.create_element();
31 27 this.rebuild();
32 28 return this;
@@ -34,7 +30,7 b' var IPython = (function (IPython) {'
34 30
35 31
36 32 CellToolbar.prototype.create_element = function () {
37 this.inner_element = $('<div/>').addClass('celltoolbar')
33 this.inner_element = $('<div/>').addClass('celltoolbar');
38 34 this.element = $('<div/>').addClass('ctb_hideshow')
39 35 .append(this.inner_element);
40 36 };
@@ -182,13 +178,14 b' var IPython = (function (IPython) {'
182 178 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
183 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 182 CellToolbar._presets[name] = preset_list;
187 $([IPython.events]).trigger('preset_added.CellToolbar', {name: name});
183 events.trigger('preset_added.CellToolbar', {name: name});
188 184 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
189 185 // In that case, activate the preset if needed.
190 if (IPython.notebook && IPython.notebook.metadata && IPython.notebook.metadata.celltoolbar === name)
191 this.activate_preset(name);
186 if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){
187 CellToolbar.activate_preset(name);
188 }
192 189 };
193 190
194 191
@@ -229,7 +226,7 b' var IPython = (function (IPython) {'
229 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 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 284 this.hide();
288 285 } else {
289 286 this.show();
@@ -347,7 +344,7 b' var IPython = (function (IPython) {'
347 344 setter(cell, !v);
348 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 408 select.change(function(){
412 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 416 IPython.CellToolbar = CellToolbar;
420 417
421 return IPython;
422 }(IPython));
418 return {'CellToolbar': CellToolbar};
419 });
@@ -1,31 +1,29 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
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 // 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) {
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
4 define([
5 'jquery',
6 'notebook/js/celltoolbar',
7 'base/js/dialog',
8 ], function($, celltoolbar, dialog) {
17 9 "use strict";
18 10
19 var CellToolbar = IPython.CellToolbar;
20
21 var raw_edit = function(cell){
22 IPython.dialog.edit_metadata(cell.metadata, function (md) {
23 cell.metadata = md;
11 var CellToolbar = celltoolbar.CellToolbar;
12
13 var raw_edit = function (cell) {
14 dialog.edit_metadata({
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 25 var add_raw_edit_button = function(div, cell) {
28 var button_container = div;
26 var button_container = $(div);
29 27 var button = $('<button/>')
30 28 .addClass("btn btn-default btn-xs")
31 29 .text("Edit Metadata")
@@ -36,11 +34,18 b''
36 34 button_container.append(button);
37 35 };
38 36
39 CellToolbar.register_callback('default.rawedit', add_raw_edit_button);
40 var example_preset = [];
41 example_preset.push('default.rawedit');
37 var register = function (notebook) {
38 CellToolbar.register_callback('default.rawedit', add_raw_edit_button);
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 console.log('Default extension for cell metadata editing loaded.');
44 var example_preset = [];
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 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
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 // CellToolbar Example
10 //============================================================================
11
12 /**
13 * Example Use for the CellToolbar library
14 * add the following to your custom.js to load
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) {
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
4 // Example Use for the CellToolbar library
5 // add the following to your custom.js to load
6 // Celltoolbar UI for slideshow
7
8 // ```
9 // $.getScript('/static/js/celltoolbarpresets/example.js');
10 // ```
11 define([
12 'jquery',
13 'notebook/js/celltoolbar',
14 ], function($, celltoolbar) {
23 15 "use strict";
24 16
25 var CellToolbar = IPython.CellToolbar;
17 var CellToolbar = celltoolbar.CellToolbar;
26 18
27 19 var example_preset = [];
28 20
@@ -32,32 +24,32 b''
32 24 var fun = function(value){
33 25 try{
34 26 if(value){
35 cell.code_mirror.setOption('readOnly','nocursor')
36 button.button('option','icons',{primary:'ui-icon-locked'})
27 cell.code_mirror.setOption('readOnly','nocursor');
28 button.button('option','icons',{primary:'ui-icon-locked'});
37 29 } else {
38 cell.code_mirror.setOption('readOnly',false)
39 button.button('option','icons',{primary:'ui-icon-unlocked'})
30 cell.code_mirror.setOption('readOnly',false);
31 button.button('option','icons',{primary:'ui-icon-unlocked'});
40 32 }
41 33 } catch(e){}
42 34
43 }
44 fun(cell.metadata.ro)
35 };
36 fun(cell.metadata.ro);
45 37 button.click(function(){
46 38 var v = cell.metadata.ro;
47 39 var locked = !v;
48 40 cell.metadata.ro = locked;
49 fun(locked)
41 fun(locked);
50 42 })
51 43 .css('height','16px')
52 44 .css('width','35px');
53 45 button_container.append(button);
54 }
46 };
55 47
56 48 CellToolbar.register_callback('example.lock',simple_button);
57 49 example_preset.push('example.lock');
58 50
59 51 var toggle_test = function(div, cell) {
60 var button_container = $(div)
52 var button_container = $(div);
61 53 var button = $('<div/>')
62 54 .button({label:String(cell.metadata.foo)}).
63 55 css('width','65px');
@@ -65,9 +57,9 b''
65 57 var v = cell.metadata.foo;
66 58 cell.metadata.foo = !v;
67 59 button.button("option","label",String(!v));
68 })
60 });
69 61 button_container.append(button);
70 }
62 };
71 63
72 64 CellToolbar.register_callback('example.toggle',toggle_test);
73 65 example_preset.push('example.toggle');
@@ -76,16 +68,16 b''
76 68 // setter
77 69 function(cell, value){
78 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 72 // set the value
81 cell.metadata.yn_test.value = value
73 cell.metadata.yn_test.value = value;
82 74 },
83 75 //geter
84 76 function(cell){ var ns = cell.metadata.yn_test;
85 77 // if the slideshow namespace does not exist return `undefined`
86 78 // (will be interpreted as `false` by checkbox) otherwise
87 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 95 // setter
104 96 function(cell,value){
105 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 99 // set the value
108 cell.metadata.test.slide_type = value
100 cell.metadata.test.slide_type = value;
109 101 },
110 102 //geter
111 103 function(cell){ var ns = cell.metadata.test;
112 104 // if the slideshow namespace does not exist return `undefined`
113 105 // (will be interpreted as `false` by checkbox) otherwise
114 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 110 CellToolbar.register_callback('example.select',select_test);
@@ -120,7 +112,7 b''
120 112
121 113 var simple_dialog = function(title,text){
122 114 var dlg = $('<div/>').attr('title',title)
123 .append($('<p/>').text(text))
115 .append($('<p/>').text(text));
124 116 $(dlg).dialog({
125 117 autoOpen: true,
126 118 height: 300,
@@ -131,24 +123,26 b''
131 123 $(this).remove();
132 124 }
133 125 });
134 }
126 };
135 127
136 128 var add_simple_dialog_button = function(div, cell) {
137 129 var help_text = ["This is the Metadata editting UI.",
138 130 "It heavily rely on plugin to work ",
139 131 "and is still under developpement. You shouldn't wait too long before",
140 132 " seeing some customisable buttons in those toolbar."
141 ].join('\n')
142 var button_container = $(div)
133 ].join('\n');
134 var button_container = $(div);
143 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 137 button_container.append(button);
146 }
147
148 CellToolbar.register_callback('example.help',add_simple_dialog_button)
149 example_preset.push('example.help')
138 };
150 139
151 CellToolbar.register_preset('Example',example_preset);
152 console.log('Example extension for metadata editing loaded.');
140 var register = function (notebook) {
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 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
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 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 // CellToolbar Example
10 //============================================================================
4 define([
5 'jquery',
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) {
13 "use strict";
14
15 var CellToolbar = IPython.CellToolbar;
12 var CellToolbar = celltoolbar.CellToolbar;
16 13 var raw_cell_preset = [];
17 14
18 15 var select_type = CellToolbar.utils.select_ui_generator([
@@ -39,7 +36,7 b''
39 36 $('<input/>').attr('type','text').attr('size','25')
40 37 .val(cell.metadata.raw_mimetype || "-")
41 38 );
42 IPython.dialog.modal({
39 dialog.modal({
43 40 title: "Raw Cell MIME Type",
44 41 body: dialog,
45 42 buttons : {
@@ -57,7 +54,7 b''
57 54 var that = $(this);
58 55 // Upon ENTER, click the OK button.
59 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 58 that.find('.btn-primary').first().click();
62 59 return false;
63 60 }
@@ -77,11 +74,13 b''
77 74 "Raw NBConvert Format"
78 75 );
79 76
80 CellToolbar.register_callback('raw_cell.select', select_type, ['raw']);
81
82 raw_cell_preset.push('raw_cell.select');
77 var register = function (notebook) {
78 CellToolbar.register_callback('raw_cell.select', select_type, ['raw']);
79 raw_cell_preset.push('raw_cell.select');
83 80
84 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
85 console.log('Raw Cell Format toolbar preset loaded.');
81 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset, notebook);
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 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
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 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 //CellToolbar Example
10 //============================================================================
11
12 // IIFE without asignement, we don't modifiy the IPython namespace
13 (function (IPython) {
4 define([
5 'jquery',
6 'notebook/js/celltoolbar',
7 ], function($, celltoolbar) {
14 8 "use strict";
15 9
16 var CellToolbar = IPython.CellToolbar;
10
11 var CellToolbar = celltoolbar.CellToolbar;
17 12 var slideshow_preset = [];
18 13
19 14 var select_type = CellToolbar.utils.select_ui_generator([
@@ -27,24 +22,25 b''
27 22 // setter
28 23 function(cell, value){
29 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 26 // set the value
32 cell.metadata.slideshow.slide_type = value
27 cell.metadata.slideshow.slide_type = value;
33 28 },
34 29 //geter
35 30 function(cell){ var ns = cell.metadata.slideshow;
36 31 // if the slideshow namespace does not exist return `undefined`
37 32 // (will be interpreted as `false` by checkbox) otherwise
38 33 // return the value
39 return (ns == undefined)? undefined: ns.slide_type
34 return (ns === undefined)? undefined: ns.slide_type;
40 35 },
41 36 "Slide Type");
42 37
43 CellToolbar.register_callback('slideshow.select',select_type);
44
45 slideshow_preset.push('slideshow.select');
46
47 CellToolbar.register_preset('Slideshow',slideshow_preset);
48 console.log('Slideshow extension for metadata editing loaded.');
38 var register = function (notebook) {
39 CellToolbar.register_callback('slideshow.select',select_type);
40 slideshow_preset.push('slideshow.select');
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 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
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 // CodeCell
10 //============================================================================
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
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) {
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 'base/js/keyboard',
9 'notebook/js/cell',
10 'notebook/js/outputarea',
11 'notebook/js/completer',
12 'notebook/js/celltoolbar',
13 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) {
45 14 "use strict";
15 var Cell = cell.Cell;
46 16
47 var utils = IPython.utils;
48 var keycodes = IPython.keyboard.keycodes;
17 /* local util for codemirror */
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
54 * it will be null and set_kernel has to be called later.
55 * @class CodeCell
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
22 * function to delete until previous non blanking space character
23 * or first multiple of 4 tabstop.
24 * @private
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 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 59 this.kernel = kernel || null;
60 this.notebook = options.notebook;
65 61 this.collapsed = false;
62 this.events = options.events;
63 this.tooltip = options.tooltip;
64 this.config = options.config;
66 65
67 66 // create all attributed in constructor function
68 67 // even if null for V8 VM optimisation
@@ -77,9 +76,11 b' var IPython = (function (IPython) {'
77 76 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 77 };
79 78
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
81
82 IPython.Cell.apply(this,[options]);
79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
80 Cell.apply(this,[{
81 config: config,
82 keyboard_manager: options.keyboard_manager,
83 events: this.events}]);
83 84
84 85 // Attributes we want to override in this subclass.
85 86 this.cell_type = "code";
@@ -109,29 +110,31 b' var IPython = (function (IPython) {'
109 110
110 111 CodeCell.msg_cells = {};
111 112
112 CodeCell.prototype = new IPython.Cell();
113 CodeCell.prototype = new Cell();
113 114
114 115 /**
115 116 * @method auto_highlight
116 117 */
117 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 122 /** @method create_element */
122 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 127 cell.attr('tabindex','2');
127 128
128 129 var input = $('<div></div>').addClass('input');
129 130 var prompt = $('<div/>').addClass('prompt input_prompt');
130 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 135 inner_cell.append(this.celltoolbar.element);
133 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 138 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
136 139 inner_cell.append(input_area);
137 140 input.append(prompt).append(inner_cell);
@@ -158,13 +161,17 b' var IPython = (function (IPython) {'
158 161 var output = $('<div></div>');
159 162 cell.append(input).append(widget_area).append(output);
160 163 this.element = cell;
161 this.output_area = new IPython.OutputArea(output, true);
162 this.completer = new IPython.Completer(this);
164 this.output_area = new outputarea.OutputArea({
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 172 /** @method bind_events */
166 173 CodeCell.prototype.bind_events = function () {
167 IPython.Cell.prototype.bind_events.apply(this);
174 Cell.prototype.bind_events.apply(this);
168 175 var that = this;
169 176
170 177 this.element.focusout(
@@ -187,7 +194,7 b' var IPython = (function (IPython) {'
187 194 // they are sent, and remove tooltip if any, except for tab again
188 195 var tooltip_closed = null;
189 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 200 var cur = editor.getCursor();
@@ -195,21 +202,21 b' var IPython = (function (IPython) {'
195 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 206 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
200 207 // browser and keyboard layout !
201 208 // Pressing '(' , request tooltip, don't forget to reappend it
202 209 // The second argument says to hide the tooltip if the docstring
203 210 // is actually empty
204 IPython.tooltip.pending(that, true);
211 this.tooltip.pending(that, true);
205 212 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
206 213 // If tooltip is active, cancel it. The call to
207 214 // remove_and_cancel_tooltip above doesn't pass, force=true.
208 215 // Because of this it won't actually close the tooltip
209 216 // if it is in sticky mode. Thus, we have to check again if it is open
210 217 // and close it with force=true.
211 if (!IPython.tooltip._hidden) {
212 IPython.tooltip.remove_and_cancel_tooltip(true);
218 if (!this.tooltip._hidden) {
219 this.tooltip.remove_and_cancel_tooltip(true);
213 220 }
214 221 // If we closed the tooltip, don't let CM or the global handlers
215 222 // handle this event.
@@ -223,12 +230,12 b' var IPython = (function (IPython) {'
223 230 return false;
224 231 }
225 232 }
226 IPython.tooltip.request(that);
233 this.tooltip.request(that);
227 234 event.stop();
228 235 return true;
229 236 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
230 237 // Tab completion.
231 IPython.tooltip.remove_and_cancel_tooltip();
238 this.tooltip.remove_and_cancel_tooltip();
232 239 if (editor.somethingSelected()) {
233 240 return false;
234 241 }
@@ -246,7 +253,7 b' var IPython = (function (IPython) {'
246 253
247 254 // keyboard event wasn't one of those unique to code cells, let's see
248 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 259 // Kernel related calls.
@@ -305,7 +312,7 b' var IPython = (function (IPython) {'
305 312 };
306 313
307 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 322 CodeCell.prototype._handle_execute_reply = function (msg) {
316 323 this.set_input_prompt(msg.content.execution_count);
317 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 332 CodeCell.prototype._handle_set_next_input = function (payload) {
326 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 346 // Basic cell manipulation.
340 347
341 348 CodeCell.prototype.select = function () {
342 var cont = IPython.Cell.prototype.select.apply(this);
349 var cont = Cell.prototype.select.apply(this);
343 350 if (cont) {
344 351 this.code_mirror.refresh();
345 352 this.auto_highlight();
@@ -348,7 +355,7 b' var IPython = (function (IPython) {'
348 355 };
349 356
350 357 CodeCell.prototype.render = function () {
351 var cont = IPython.Cell.prototype.render.apply(this);
358 var cont = Cell.prototype.render.apply(this);
352 359 // Always execute, even if we are already in the rendered state
353 360 return cont;
354 361 };
@@ -451,7 +458,7 b' var IPython = (function (IPython) {'
451 458 // JSON serialization
452 459
453 460 CodeCell.prototype.fromJSON = function (data) {
454 IPython.Cell.prototype.fromJSON.apply(this, arguments);
461 Cell.prototype.fromJSON.apply(this, arguments);
455 462 if (data.cell_type === 'code') {
456 463 if (data.input !== undefined) {
457 464 this.set_text(data.input);
@@ -479,7 +486,7 b' var IPython = (function (IPython) {'
479 486
480 487
481 488 CodeCell.prototype.toJSON = function () {
482 var data = IPython.Cell.prototype.toJSON.apply(this);
489 var data = Cell.prototype.toJSON.apply(this);
483 490 data.input = this.get_text();
484 491 // is finite protect against undefined and '*' value
485 492 if (isFinite(this.input_prompt_number)) {
@@ -499,11 +506,11 b' var IPython = (function (IPython) {'
499 506 * @return is the action being taken
500 507 */
501 508 CodeCell.prototype.unselect = function () {
502 var cont = IPython.Cell.prototype.unselect.apply(this);
509 var cont = Cell.prototype.unselect.apply(this);
503 510 if (cont) {
504 511 // When a code cell is usnelected, make sure that the corresponding
505 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 514 if (this.completer !== null) {
508 515 this.completer.close();
509 516 }
@@ -511,7 +518,8 b' var IPython = (function (IPython) {'
511 518 return cont;
512 519 };
513 520
521 // Backwards compatability.
514 522 IPython.CodeCell = CodeCell;
515 523
516 return IPython;
517 }(IPython));
524 return {'CodeCell': CodeCell};
525 });
@@ -7,10 +7,15 b" CodeMirror.requireMode('python',function(){"
7 7 "use strict";
8 8
9 9 CodeMirror.defineMode("ipython", function(conf, parserConf) {
10
11 parserConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
12 parserConf.name = 'python'
13 return CodeMirror.getMode(conf, parserConf);
10 var pythonConf = {};
11 for (var prop in parserConf) {
12 if (parserConf.hasOwnProperty(prop)) {
13 pythonConf[prop] = parserConf[prop];
14 }
15 }
16 pythonConf.name = 'python';
17 pythonConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
18 return CodeMirror.getMode(conf, pythonConf);
14 19 }, 'python');
15 20
16 21 CodeMirror.defineMIME("text/x-ipython", "ipython");
@@ -8,7 +8,6 b''
8 8
9 9 CodeMirror.requireMode('gfm', function(){
10 10 CodeMirror.requireMode('stex', function(){
11 console.log('defining custom mode...');
12 11 CodeMirror.defineMode("ipythongfm", function(config, parserConfig) {
13 12
14 13 var gfm_mode = CodeMirror.getMode(config, "gfm");
@@ -1,17 +1,17 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 // Completer
5 //
6 // Completer is be a class that takes a cell instance.
7
8 var IPython = (function (IPython) {
9 // that will prevent us from misspelling
4 define([
5 'base/js/namespace',
6 'jquery',
7 'base/js/utils',
8 'base/js/keyboard',
9 'notebook/js/contexthint',
10 ], function(IPython, $, utils, keyboard) {
10 11 "use strict";
11 12
12 13 // easier key mapping
13 var keycodes = IPython.keyboard.keycodes;
14 var utils = IPython.utils;
14 var keycodes = keyboard.keycodes;
15 15
16 16 var prepend_n_prc = function(str, n) {
17 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 82 this.cell = cell;
83 83 this.editor = cell.code_mirror;
84 84 var that = this;
85 $([IPython.events]).on('status_busy.Kernel', function () {
85 events.on('status_busy.Kernel', function () {
86 86 that.skip_kernel_completion = true;
87 87 });
88 $([IPython.events]).on('status_idle.Kernel', function () {
88 events.on('status_idle.Kernel', function () {
89 89 that.skip_kernel_completion = false;
90 90 });
91 91 };
@@ -351,6 +351,18 b' var IPython = (function (IPython) {'
351 351 }
352 352 index = Math.min(Math.max(index, 0), options.length-1);
353 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 366 } else if (code == keycodes.left || code == keycodes.right) {
355 367 this.close();
356 368 }
@@ -379,7 +391,9 b' var IPython = (function (IPython) {'
379 391 that.carry_on_completion();
380 392 }, 50);
381 393 };
394
395 // For backwards compatability.
382 396 IPython.Completer = Completer;
383 397
384 return IPython;
385 }(IPython));
398 return {'Completer': Completer};
399 });
@@ -1,28 +1,9 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
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 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 // Notebook
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 var IPython = (function (IPython) {
4 define([], function() {
18 5 "use strict";
19 /**
20 * A place where some stuff can be confugured.
21 *
22 * @class config
23 * @static
24 *
25 **/
6
26 7 var default_config = {
27 8 /**
28 9 * Dictionary of object to autodetect highlight mode for code cell.
@@ -50,30 +31,25 b' var IPython = (function (IPython) {'
50 31 * cell_magic_highlight['javascript'] = {'reg':[/^var/]}
51 32 */
52 33 cell_magic_highlight : {
53 'magic_javascript' :{'reg':[/^%%javascript/]}
54 ,'magic_perl' :{'reg':[/^%%perl/]}
55 ,'magic_ruby' :{'reg':[/^%%ruby/]}
56 ,'magic_python' :{'reg':[/^%%python3?/]}
57 ,'magic_shell' :{'reg':[/^%%bash/]}
58 ,'magic_r' :{'reg':[/^%%R/]}
59 ,'magic_text/x-cython' :{'reg':[/^%%cython/]}
60 },
34 'magic_javascript' :{'reg':[/^%%javascript/]},
35 'magic_perl' :{'reg':[/^%%perl/]},
36 'magic_ruby' :{'reg':[/^%%ruby/]},
37 'magic_python' :{'reg':[/^%%python3?/]},
38 'magic_shell' :{'reg':[/^%%bash/]},
39 'magic_r' :{'reg':[/^%%R/]},
40 'magic_text/x-cython' :{'reg':[/^%%cython/]},
41 },
61 42
62 43 /**
63 44 * same as `cell_magic_highlight` but for raw cells
64 45 * @attribute raw_cell_highlight
65 46 */
66 47 raw_cell_highlight : {
67 'diff' :{'reg':[/^diff/]}
68 },
69
70 };
71
72 // use the same method to merge user configuration
73 IPython.config = {};
74 $.extend(IPython.config, default_config);
75
76 return IPython;
77
78 }(IPython));
79
48 'diff' :{'reg':[/^diff/]}
49 },
50 };
51
52 return {
53 'default_config': default_config,
54 };
55 });
@@ -1,12 +1,15 b''
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
1 4 // highly adapted for codemiror jshint
2 (function () {
5 define([], function() {
3 6 "use strict";
4 7
5 function forEach(arr, f) {
8 var forEach = function(arr, f) {
6 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 13 if (!Array.prototype.indexOf) {
11 14 var i = arr.length;
12 15 while (i--) {
@@ -17,7 +20,7 b''
17 20 return false;
18 21 }
19 22 return arr.indexOf(item) != -1;
20 }
23 };
21 24
22 25 CodeMirror.contextHint = function (editor) {
23 26 // Find the token at the cursor
@@ -26,7 +29,7 b''
26 29 tprop = token;
27 30 // If it's not a 'word-style' token, ignore the token.
28 31 // If it is a property, find out what it is a property of.
29 var list = new Array();
32 var list = [];
30 33 var clist = getCompletions(token, editor);
31 34 for (var i = 0; i < clist.length; i++) {
32 35 list.push({
@@ -40,55 +43,56 b''
40 43 line: cur.line,
41 44 ch: token.end
42 45 }
43 })
46 });
44 47 }
45 48 return list;
46 }
49 };
47 50
48 51 // find all 'words' of current cell
49 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) {
56 if (!arrayContains(found, str)) found.push(str);
57 }
58 function maybeAdd(str) {
59 if (!arrayContains(found, str)) found.push(str);
60 }
58 61
59 // loop through all token on all lines
60 var lineCount = editor.lineCount();
61 // loop on line
62 for (var l = 0; l < lineCount; l++) {
63 var line = editor.getLine(l);
64 //loop on char
65 for (var c = 1; c < line.length; c++) {
66 var tk = editor.getTokenAt({
67 line: l,
68 ch: c
69 });
70 // if token has a class, it has geat chances of beeing
71 // of interest. Add it to the list of possible completions.
72 // we could skip token of ClassName 'comment'
73 // or 'number' and 'operator'
74 if (tk.className != null) {
75 maybeAdd(tk.string);
76 }
77 // jump to char after end of current token
78 c = tk.end;
62 // loop through all token on all lines
63 var lineCount = editor.lineCount();
64 // loop on line
65 for (var l = 0; l < lineCount; l++) {
66 var line = editor.getLine(l);
67 //loop on char
68 for (var c = 1; c < line.length; c++) {
69 var tk = editor.getTokenAt({
70 line: l,
71 ch: c
72 });
73 // if token has a class, it has geat chances of beeing
74 // of interest. Add it to the list of possible completions.
75 // we could skip token of ClassName 'comment'
76 // or 'number' and 'operator'
77 if (tk.className !== null) {
78 maybeAdd(tk.string);
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
85 function getCompletions(token, editor) {
87 var getCompletions = function(token, editor) {
86 88 var candidates = getAllTokens(editor);
87 89 // filter all token that have a common start (but nox exactly) the lenght of the current token
88 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 93 var filterd = candidates.filter(lambda);
92 94 return filterd;
93 }
94 })();
95 };
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 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 // Keyboard management
10 //============================================================================
11
12 var IPython = (function (IPython) {
4 define([
5 'base/js/namespace',
6 'jquery',
7 'base/js/utils',
8 'base/js/keyboard',
9 ], function(IPython, $, utils, keyboard) {
13 10 "use strict";
11
12 var browser = utils.browser[0];
13 var platform = utils.platform;
14 14
15 var browser = IPython.utils.browser[0];
16 var platform = IPython.utils.platform;
15 // Main keyboard manager for the notebook
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 = {
21 'shift' : {
22 help : '',
23 help_index : '',
24 handler : function (event) {
25 // ignore shift keydown
26 return true;
27 }
28 },
29 'shift-enter' : {
30 help : 'run cell, select below',
31 help_index : 'ba',
32 handler : function (event) {
33 IPython.notebook.execute_cell_and_select_below();
34 return false;
35 }
36 },
37 'ctrl-enter' : {
38 help : 'run cell',
39 help_index : 'bb',
40 handler : function (event) {
41 IPython.notebook.execute_cell();
42 return false;
43 }
44 },
45 'alt-enter' : {
46 help : 'run cell, insert below',
47 help_index : 'bc',
48 handler : function (event) {
49 IPython.notebook.execute_cell_and_insert_below();
50 return false;
40 KeyboardManager.prototype.get_default_common_shortcuts = function() {
41 var that = this;
42 var shortcuts = {
43 'shift' : {
44 help : '',
45 help_index : '',
46 handler : function (event) {
47 // ignore shift keydown
48 return true;
49 }
50 },
51 'shift-enter' : {
52 help : 'run cell, select below',
53 help_index : 'ba',
54 handler : function (event) {
55 that.notebook.execute_cell_and_select_below();
56 return false;
57 }
58 },
59 'ctrl-enter' : {
60 help : 'run cell',
61 help_index : 'bb',
62 handler : function (event) {
63 that.notebook.execute_cell();
64 return false;
65 }
66 },
67 'alt-enter' : {
68 help : 'run cell, insert below',
69 help_index : 'bc',
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') {
56 default_common_shortcuts['cmd-s'] =
57 {
58 help : 'save notebook',
59 help_index : 'fb',
103 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
104 var that = this;
105 return {
106 'esc' : {
107 help : 'command mode',
108 help_index : 'aa',
60 109 handler : function (event) {
61 IPython.notebook.save_checkpoint();
62 event.preventDefault();
110 that.notebook.command_mode();
63 111 return false;
64 112 }
65 };
66 } else {
67 default_common_shortcuts['ctrl-s'] =
68 {
69 help : 'save notebook',
70 help_index : 'fb',
113 },
114 'ctrl-m' : {
115 help : 'command mode',
116 help_index : 'ab',
71 117 handler : function (event) {
72 IPython.notebook.save_checkpoint();
73 event.preventDefault();
118 that.notebook.command_mode();
74 119 return false;
75 120 }
76 };
77 }
78
79 // Edit mode defaults
80
81 var default_edit_shortcuts = {
82 'esc' : {
83 help : 'command mode',
84 help_index : 'aa',
85 handler : function (event) {
86 IPython.notebook.command_mode();
87 return false;
88 }
89 },
90 'ctrl-m' : {
91 help : 'command mode',
92 help_index : 'ab',
93 handler : function (event) {
94 IPython.notebook.command_mode();
95 return false;
96 }
97 },
98 'up' : {
99 help : '',
100 help_index : '',
101 handler : function (event) {
102 var index = IPython.notebook.get_selected_index();
103 var cell = IPython.notebook.get_cell(index);
104 if (cell && cell.at_top() && index !== 0) {
105 event.preventDefault();
106 IPython.notebook.command_mode();
107 IPython.notebook.select_prev();
108 IPython.notebook.edit_mode();
109 var cm = IPython.notebook.get_selected_cell().code_mirror;
110 cm.setCursor(cm.lastLine(), 0);
111 return false;
112 } else if (cell) {
113 var cm = cell.code_mirror;
114 cm.execCommand('goLineUp');
121 },
122 'up' : {
123 help : '',
124 help_index : '',
125 handler : function (event) {
126 var index = that.notebook.get_selected_index();
127 var cell = that.notebook.get_cell(index);
128 if (cell && cell.at_top() && index !== 0) {
129 event.preventDefault();
130 that.notebook.command_mode();
131 that.notebook.select_prev();
132 that.notebook.edit_mode();
133 var cm = that.notebook.get_selected_cell().code_mirror;
134 cm.setCursor(cm.lastLine(), 0);
135 return false;
136 } else if (cell) {
137 var cm = cell.code_mirror;
138 cm.execCommand('goLineUp');
139 return false;
140 }
141 }
142 },
143 'down' : {
144 help : '',
145 help_index : '',
146 handler : function (event) {
147 var index = that.notebook.get_selected_index();
148 var cell = that.notebook.get_cell(index);
149 if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
150 event.preventDefault();
151 that.notebook.command_mode();
152 that.notebook.select_next();
153 that.notebook.edit_mode();
154 var cm = that.notebook.get_selected_cell().code_mirror;
155 cm.setCursor(0, 0);
156 return false;
157 } else {
158 var cm = cell.code_mirror;
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 169 return false;
116 170 }
117 }
118 },
119 'down' : {
120 help : '',
121 help_index : '',
122 handler : function (event) {
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');
171 },
172 'ctrl-shift-subtract' : {
173 help : '',
174 help_index : 'eb',
175 handler : function (event) {
176 that.notebook.split_cell();
136 177 return false;
137 178 }
138 }
139 },
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 },
179 },
180 };
156 181 };
157 182
158 // Command mode defaults
159
160 var default_command_shortcuts = {
161 'enter' : {
162 help : 'edit mode',
163 help_index : 'aa',
164 handler : function (event) {
165 IPython.notebook.edit_mode();
166 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();
183 KeyboardManager.prototype.get_default_command_shortcuts = function() {
184 var that = this;
185 return {
186 'enter' : {
187 help : 'edit mode',
188 help_index : 'aa',
189 handler : function (event) {
190 that.notebook.edit_mode();
191 return false;
177 192 }
178 return false;
179 }
180 },
181 'down' : {
182 help : 'select next cell',
183 help_index : 'db',
184 handler : function (event) {
185 var index = IPython.notebook.get_selected_index();
186 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
187 IPython.notebook.select_next();
188 IPython.notebook.focus_cell();
193 },
194 'up' : {
195 help : 'select previous cell',
196 help_index : 'da',
197 handler : function (event) {
198 var index = that.notebook.get_selected_index();
199 if (index !== 0 && index !== null) {
200 that.notebook.select_prev();
201 that.notebook.focus_cell();
202 }
203 return false;
189 204 }
190 return false;
191 }
192 },
193 'k' : {
194 help : 'select previous cell',
195 help_index : 'dc',
196 handler : function (event) {
197 var index = IPython.notebook.get_selected_index();
198 if (index !== 0 && index !== null) {
199 IPython.notebook.select_prev();
200 IPython.notebook.focus_cell();
205 },
206 'down' : {
207 help : 'select next cell',
208 help_index : 'db',
209 handler : function (event) {
210 var index = that.notebook.get_selected_index();
211 if (index !== (that.notebook.ncells()-1) && index !== null) {
212 that.notebook.select_next();
213 that.notebook.focus_cell();
214 }
215 return false;
201 216 }
202 return false;
203 }
204 },
205 'j' : {
206 help : 'select next cell',
207 help_index : 'dd',
208 handler : function (event) {
209 var index = IPython.notebook.get_selected_index();
210 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
211 IPython.notebook.select_next();
212 IPython.notebook.focus_cell();
217 },
218 'k' : {
219 help : 'select previous cell',
220 help_index : 'dc',
221 handler : function (event) {
222 var index = that.notebook.get_selected_index();
223 if (index !== 0 && index !== null) {
224 that.notebook.select_prev();
225 that.notebook.focus_cell();
226 }
227 return false;
213 228 }
214 return false;
215 }
216 },
217 'x' : {
218 help : 'cut cell',
219 help_index : 'ee',
220 handler : function (event) {
221 IPython.notebook.cut_cell();
222 return false;
223 }
224 },
225 'c' : {
226 help : 'copy cell',
227 help_index : 'ef',
228 handler : function (event) {
229 IPython.notebook.copy_cell();
230 return false;
231 }
232 },
233 'shift-v' : {
234 help : 'paste cell above',
235 help_index : 'eg',
236 handler : function (event) {
237 IPython.notebook.paste_cell_above();
238 return false;
239 }
240 },
241 'v' : {
242 help : 'paste cell below',
243 help_index : 'eh',
244 handler : function (event) {
245 IPython.notebook.paste_cell_below();
246 return false;
247 }
248 },
249 'd' : {
250 help : 'delete cell (press twice)',
251 help_index : 'ej',
252 count: 2,
253 handler : function (event) {
254 IPython.notebook.delete_cell();
255 return false;
256 }
257 },
258 'a' : {
259 help : 'insert cell above',
260 help_index : 'ec',
261 handler : function (event) {
262 IPython.notebook.insert_cell_above();
263 IPython.notebook.select_prev();
264 IPython.notebook.focus_cell();
265 return false;
266 }
267 },
268 'b' : {
269 help : 'insert cell below',
270 help_index : 'ed',
271 handler : function (event) {
272 IPython.notebook.insert_cell_below();
273 IPython.notebook.select_next();
274 IPython.notebook.focus_cell();
275 return false;
276 }
277 },
278 'y' : {
279 help : 'to code',
280 help_index : 'ca',
281 handler : function (event) {
282 IPython.notebook.to_code();
283 return false;
284 }
285 },
286 'm' : {
287 help : 'to markdown',
288 help_index : 'cb',
289 handler : function (event) {
290 IPython.notebook.to_markdown();
291 return false;
292 }
293 },
294 'r' : {
295 help : 'to raw',
296 help_index : 'cc',
297 handler : function (event) {
298 IPython.notebook.to_raw();
299 return false;
300 }
301 },
302 '1' : {
303 help : 'to heading 1',
304 help_index : 'cd',
305 handler : function (event) {
306 IPython.notebook.to_heading(undefined, 1);
307 return false;
308 }
309 },
310 '2' : {
311 help : 'to heading 2',
312 help_index : 'ce',
313 handler : function (event) {
314 IPython.notebook.to_heading(undefined, 2);
315 return false;
316 }
317 },
318 '3' : {
319 help : 'to heading 3',
320 help_index : 'cf',
321 handler : function (event) {
322 IPython.notebook.to_heading(undefined, 3);
323 return false;
324 }
325 },
326 '4' : {
327 help : 'to heading 4',
328 help_index : 'cg',
329 handler : function (event) {
330 IPython.notebook.to_heading(undefined, 4);
331 return false;
332 }
333 },
334 '5' : {
335 help : 'to heading 5',
336 help_index : 'ch',
337 handler : function (event) {
338 IPython.notebook.to_heading(undefined, 5);
339 return false;
340 }
341 },
342 '6' : {
343 help : 'to heading 6',
344 help_index : 'ci',
345 handler : function (event) {
346 IPython.notebook.to_heading(undefined, 6);
347 return false;
348 }
349 },
350 'o' : {
351 help : 'toggle output',
352 help_index : 'gb',
353 handler : function (event) {
354 IPython.notebook.toggle_output();
355 return false;
356 }
357 },
358 'shift-o' : {
359 help : 'toggle output scrolling',
360 help_index : 'gc',
361 handler : function (event) {
362 IPython.notebook.toggle_output_scroll();
363 return false;
364 }
365 },
366 's' : {
367 help : 'save notebook',
368 help_index : 'fa',
369 handler : function (event) {
370 IPython.notebook.save_checkpoint();
371 return false;
372 }
373 },
374 'ctrl-j' : {
375 help : 'move cell down',
376 help_index : 'eb',
377 handler : function (event) {
378 IPython.notebook.move_cell_down();
379 return false;
380 }
381 },
382 'ctrl-k' : {
383 help : 'move cell up',
384 help_index : 'ea',
385 handler : function (event) {
386 IPython.notebook.move_cell_up();
387 return false;
388 }
389 },
390 'l' : {
391 help : 'toggle line numbers',
392 help_index : 'ga',
393 handler : function (event) {
394 IPython.notebook.cell_toggle_line_numbers();
395 return false;
396 }
397 },
398 'i' : {
399 help : 'interrupt kernel (press twice)',
400 help_index : 'ha',
401 count: 2,
402 handler : function (event) {
403 IPython.notebook.kernel.interrupt();
404 return false;
405 }
406 },
407 '0' : {
408 help : 'restart kernel (press twice)',
409 help_index : 'hb',
410 count: 2,
411 handler : function (event) {
412 IPython.notebook.restart_kernel();
413 return false;
414 }
415 },
416 'h' : {
417 help : 'keyboard shortcuts',
418 help_index : 'ge',
419 handler : function (event) {
420 IPython.quick_help.show_keyboard_shortcuts();
421 return false;
422 }
423 },
424 'z' : {
425 help : 'undo last delete',
426 help_index : 'ei',
427 handler : function (event) {
428 IPython.notebook.undelete_cell();
429 return false;
430 }
431 },
432 'shift-m' : {
433 help : 'merge cell below',
434 help_index : 'ek',
435 handler : function (event) {
436 IPython.notebook.merge_cell_below();
437 return false;
438 }
439 },
440 'q' : {
441 help : 'close pager',
442 help_index : 'gd',
443 handler : function (event) {
444 IPython.pager.collapse();
445 return false;
446 }
447 },
448 };
449
450
451 // Main keyboard manager for the notebook
452
453 var ShortcutManager = IPython.keyboard.ShortcutManager;
454 var keycodes = IPython.keyboard.keycodes;
455
456 var KeyboardManager = function () {
457 this.mode = 'command';
458 this.enabled = true;
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);
229 },
230 'j' : {
231 help : 'select next cell',
232 help_index : 'dd',
233 handler : function (event) {
234 var index = that.notebook.get_selected_index();
235 if (index !== (that.notebook.ncells()-1) && index !== null) {
236 that.notebook.select_next();
237 that.notebook.focus_cell();
238 }
239 return false;
240 }
241 },
242 'x' : {
243 help : 'cut cell',
244 help_index : 'ee',
245 handler : function (event) {
246 that.notebook.cut_cell();
247 return false;
248 }
249 },
250 'c' : {
251 help : 'copy cell',
252 help_index : 'ef',
253 handler : function (event) {
254 that.notebook.copy_cell();
255 return false;
256 }
257 },
258 'shift-v' : {
259 help : 'paste cell above',
260 help_index : 'eg',
261 handler : function (event) {
262 that.notebook.paste_cell_above();
263 return false;
264 }
265 },
266 'v' : {
267 help : 'paste cell below',
268 help_index : 'eh',
269 handler : function (event) {
270 that.notebook.paste_cell_below();
271 return false;
272 }
273 },
274 'd' : {
275 help : 'delete cell (press twice)',
276 help_index : 'ej',
277 count: 2,
278 handler : function (event) {
279 that.notebook.delete_cell();
280 return false;
281 }
282 },
283 'a' : {
284 help : 'insert cell above',
285 help_index : 'ec',
286 handler : function (event) {
287 that.notebook.insert_cell_above();
288 that.notebook.select_prev();
289 that.notebook.focus_cell();
290 return false;
291 }
292 },
293 'b' : {
294 help : 'insert cell below',
295 help_index : 'ed',
296 handler : function (event) {
297 that.notebook.insert_cell_below();
298 that.notebook.select_next();
299 that.notebook.focus_cell();
300 return false;
301 }
302 },
303 'y' : {
304 help : 'to code',
305 help_index : 'ca',
306 handler : function (event) {
307 that.notebook.to_code();
308 return false;
309 }
310 },
311 'm' : {
312 help : 'to markdown',
313 help_index : 'cb',
314 handler : function (event) {
315 that.notebook.to_markdown();
316 return false;
317 }
318 },
319 'r' : {
320 help : 'to raw',
321 help_index : 'cc',
322 handler : function (event) {
323 that.notebook.to_raw();
324 return false;
325 }
326 },
327 '1' : {
328 help : 'to heading 1',
329 help_index : 'cd',
330 handler : function (event) {
331 that.notebook.to_heading(undefined, 1);
332 return false;
333 }
334 },
335 '2' : {
336 help : 'to heading 2',
337 help_index : 'ce',
338 handler : function (event) {
339 that.notebook.to_heading(undefined, 2);
340 return false;
341 }
342 },
343 '3' : {
344 help : 'to heading 3',
345 help_index : 'cf',
346 handler : function (event) {
347 that.notebook.to_heading(undefined, 3);
348 return false;
349 }
350 },
351 '4' : {
352 help : 'to heading 4',
353 help_index : 'cg',
354 handler : function (event) {
355 that.notebook.to_heading(undefined, 4);
356 return false;
357 }
358 },
359 '5' : {
360 help : 'to heading 5',
361 help_index : 'ch',
362 handler : function (event) {
363 that.notebook.to_heading(undefined, 5);
364 return false;
365 }
366 },
367 '6' : {
368 help : 'to heading 6',
369 help_index : 'ci',
370 handler : function (event) {
371 that.notebook.to_heading(undefined, 6);
372 return false;
373 }
374 },
375 'o' : {
376 help : 'toggle output',
377 help_index : 'gb',
378 handler : function (event) {
379 that.notebook.toggle_output();
380 return false;
381 }
382 },
383 'shift-o' : {
384 help : 'toggle output scrolling',
385 help_index : 'gc',
386 handler : function (event) {
387 that.notebook.toggle_output_scroll();
388 return false;
389 }
390 },
391 's' : {
392 help : 'save notebook',
393 help_index : 'fa',
394 handler : function (event) {
395 that.notebook.save_checkpoint();
396 return false;
397 }
398 },
399 'ctrl-j' : {
400 help : 'move cell down',
401 help_index : 'eb',
402 handler : function (event) {
403 that.notebook.move_cell_down();
404 return false;
405 }
406 },
407 'ctrl-k' : {
408 help : 'move cell up',
409 help_index : 'ea',
410 handler : function (event) {
411 that.notebook.move_cell_up();
412 return false;
413 }
414 },
415 'l' : {
416 help : 'toggle line numbers',
417 help_index : 'ga',
418 handler : function (event) {
419 that.notebook.cell_toggle_line_numbers();
420 return false;
421 }
422 },
423 'i' : {
424 help : 'interrupt kernel (press twice)',
425 help_index : 'ha',
426 count: 2,
427 handler : function (event) {
428 that.notebook.kernel.interrupt();
429 return false;
430 }
431 },
432 '0' : {
433 help : 'restart kernel (press twice)',
434 help_index : 'hb',
435 count: 2,
436 handler : function (event) {
437 that.notebook.restart_kernel();
438 return false;
439 }
440 },
441 'h' : {
442 help : 'keyboard shortcuts',
443 help_index : 'ge',
444 handler : function (event) {
445 that.quick_help.show_keyboard_shortcuts();
446 return false;
447 }
448 },
449 'z' : {
450 help : 'undo last delete',
451 help_index : 'ei',
452 handler : function (event) {
453 that.notebook.undelete_cell();
454 return false;
455 }
456 },
457 'shift-m' : {
458 help : 'merge cell below',
459 help_index : 'ek',
460 handler : function (event) {
461 that.notebook.merge_cell_below();
462 return false;
463 }
464 },
465 'q' : {
466 help : 'close pager',
467 help_index : 'gd',
468 handler : function (event) {
469 that.pager.collapse();
470 return false;
471 }
472 },
473 };
466 474 };
467 475
468 476 KeyboardManager.prototype.bind_events = function () {
@@ -473,7 +481,7 b' var IPython = (function (IPython) {'
473 481 };
474 482
475 483 KeyboardManager.prototype.handle_keydown = function (event) {
476 var notebook = IPython.notebook;
484 var notebook = this.notebook;
477 485
478 486 if (event.which === keycodes.esc) {
479 487 // Intercept escape at highest level to avoid closing
@@ -545,18 +553,14 b' var IPython = (function (IPython) {'
545 553 // is_focused must be used to check for the case where an element within
546 554 // the element being removed is focused.
547 555 e.on('remove', function () {
548 if (IPython.utils.is_focused(e[0])) {
556 if (utils.is_focused(e[0])) {
549 557 that.enable();
550 558 }
551 559 });
552 560 };
553 561
554
555 IPython.default_common_shortcuts = default_common_shortcuts;
556 IPython.default_edit_shortcuts = default_edit_shortcuts;
557 IPython.default_command_shortcuts = default_command_shortcuts;
562 // For backwards compatability.
558 563 IPython.KeyboardManager = KeyboardManager;
559 564
560 return IPython;
561
562 }(IPython));
565 return {'KeyboardManager': KeyboardManager};
566 });
@@ -1,19 +1,15 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Layout
10 //============================================================================
11
12 var IPython = (function (IPython) {
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 ], function(IPython, $) {
13 8 "use strict";
14 9
15 10 var LayoutManager = function () {
16 11 this.bind_events();
12 this.pager = undefined;
17 13 };
18 14
19 15 LayoutManager.prototype.bind_events = function () {
@@ -43,19 +39,20 b' var IPython = (function (IPython) {'
43 39 var app_height = this.app_height(); // content height
44 40
45 41 $('#ipython-main-app').height(app_height); // content+padding+border height
46
47 var pager_height = IPython.pager.percentage_height*app_height;
48 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
49 $('div#pager').outerHeight(pager_height);
50 if (IPython.pager.expanded) {
51 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
52 } else {
53 $('div#notebook').outerHeight(app_height-pager_splitter_height);
42 if (this.pager) {
43 var pager_height = this.pager.percentage_height*app_height;
44 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
45 $('div#pager').outerHeight(pager_height);
46 if (this.pager.expanded) {
47 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
48 } else {
49 $('div#notebook').outerHeight(app_height-pager_splitter_height);
50 }
54 51 }
55 52 };
56 53
54 // Backwards compatability.
57 55 IPython.LayoutManager = LayoutManager;
58 56
59 return IPython;
60
61 }(IPython));
57 return {'LayoutManager': LayoutManager};
58 });
@@ -1,78 +1,96 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 // On document ready
10 //============================================================================
11
12 // for the time beeing, we have to pass marked as a parameter here,
13 // as injecting require.js make marked not to put itself in the globals,
14 // which make both this file fail at setting marked configuration, and textcell.js
15 // which search marked into global.
16 require(['components/marked/lib/marked',
17 'widgets/js/init',
18 'components/bootstrap-tour/build/js/bootstrap-tour.min'],
19
20 function (marked) {
4 require([
5 'base/js/namespace',
6 'jquery',
7 'notebook/js/notebook',
8 'base/js/utils',
9 'base/js/page',
10 'notebook/js/layoutmanager',
11 'base/js/events',
12 'auth/js/loginwidget',
13 'notebook/js/maintoolbar',
14 'notebook/js/pager',
15 'notebook/js/quickhelp',
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 43 "use strict";
22 44
23 window.marked = marked;
24
25 // monkey patch CM to be able to syntax highlight cell magics
26 // bug reported upstream,
27 // see https://github.com/marijnh/CodeMirror2/issues/670
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')
45 var common_options = {
46 base_url : utils.get_body_data("baseUrl"),
47 ws_url : IPython.utils.get_body_data("wsUrl"),
48 notebook_path : utils.get_body_data("notebookPath"),
49 notebook_name : utils.get_body_data('notebookName')
54 50 };
55 51
56 IPython.page = new IPython.Page();
57 IPython.layout_manager = new IPython.LayoutManager();
58 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
59 IPython.quick_help = new IPython.QuickHelp();
60 try {
61 IPython.tour = new IPython.NotebookTour();
62 } catch (e) {
63 console.log("Failed to instantiate Notebook Tour", e);
64 }
65 IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts);
66 IPython.notebook = new IPython.Notebook('div#notebook', opts);
67 IPython.keyboard_manager = new IPython.KeyboardManager();
68 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
69 IPython.menubar = new IPython.MenuBar('#menubar', opts);
70 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container');
71 IPython.tooltip = new IPython.Tooltip();
72 IPython.notification_area = new IPython.NotificationArea('#notification_area');
73 IPython.notification_area.init_notification_widgets();
74
75 IPython.layout_manager.do_resize();
52 var user_config = $.extend({}, config.default_config);
53 var page = new page.Page();
54 var layout_manager = new layoutmanager.LayoutManager();
55 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
56 layout_manager: layout_manager,
57 events: events});
58 var keyboard_manager = new keyboardmanager.KeyboardManager({
59 pager: pager,
60 events: events});
61 var save_widget = new savewidget.SaveWidget('span#save_widget', {
62 events: events,
63 keyboard_manager: keyboard_manager});
64 var notebook = new notebook.Notebook('div#notebook', $.extend({
65 events: events,
66 keyboard_manager: keyboard_manager,
67 save_widget: save_widget,
68 config: user_config},
69 common_options));
70 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
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 95 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
78 96 '<span id="test2" style="font-weight: bold;">x</span>'+
@@ -85,43 +103,37 b' function (marked) {'
85 103 }
86 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 109 var first_load = function () {
92 IPython.layout_manager.do_resize();
110 layout_manager.do_resize();
93 111 var hash = document.location.hash;
94 112 if (hash) {
95 113 document.location.hash = '';
96 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 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);
104 $([IPython.events]).trigger('app_initialized.NotebookApp');
105 IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path);
122 IPython.page = page;
123 IPython.layout_manager = layout_manager;
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 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // ToolBar
10 //============================================================================
11
12 var IPython = (function (IPython) {
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 'notebook/js/toolbar',
8 'notebook/js/celltoolbar',
9 ], function(IPython, $, toolbar, celltoolbar) {
13 10 "use strict";
14 11
15 var MainToolBar = function (selector) {
16 IPython.ToolBar.apply(this, arguments);
12 var MainToolBar = function (selector, options) {
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 24 this.construct();
18 25 this.add_celltype_list();
19 26 this.add_celltoolbar_list();
20 27 this.bind_events();
21 28 };
22 29
23 MainToolBar.prototype = new IPython.ToolBar();
30 MainToolBar.prototype = new toolbar.ToolBar();
24 31
25 32 MainToolBar.prototype.construct = function () {
33 var that = this;
26 34 this.add_buttons_group([
27 35 {
28 36 id : 'save_b',
29 37 label : 'Save and Checkpoint',
30 icon : 'icon-save',
38 icon : 'fa-save',
31 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 47 id : 'insert_below_b',
40 48 label : 'Insert Cell Below',
41 icon : 'icon-plus-sign',
49 icon : 'fa-plus',
42 50 callback : function () {
43 IPython.notebook.insert_cell_below('code');
44 IPython.notebook.select_next();
45 IPython.notebook.focus_cell();
51 that.notebook.insert_cell_below('code');
52 that.notebook.select_next();
53 that.notebook.focus_cell();
46 54 }
47 55 }
48 56 ],'insert_above_below');
@@ -51,25 +59,25 b' var IPython = (function (IPython) {'
51 59 {
52 60 id : 'cut_b',
53 61 label : 'Cut Cell',
54 icon : 'icon-cut',
62 icon : 'fa-cut',
55 63 callback : function () {
56 IPython.notebook.cut_cell();
64 that.notebook.cut_cell();
57 65 }
58 66 },
59 67 {
60 68 id : 'copy_b',
61 69 label : 'Copy Cell',
62 icon : 'icon-copy',
70 icon : 'fa-copy',
63 71 callback : function () {
64 IPython.notebook.copy_cell();
72 that.notebook.copy_cell();
65 73 }
66 74 },
67 75 {
68 76 id : 'paste_b',
69 77 label : 'Paste Cell Below',
70 icon : 'icon-paste',
78 icon : 'fa-paste',
71 79 callback : function () {
72 IPython.notebook.paste_cell_below();
80 that.notebook.paste_cell_below();
73 81 }
74 82 }
75 83 ],'cut_copy_paste');
@@ -78,17 +86,17 b' var IPython = (function (IPython) {'
78 86 {
79 87 id : 'move_up_b',
80 88 label : 'Move Cell Up',
81 icon : 'icon-arrow-up',
89 icon : 'fa-arrow-up',
82 90 callback : function () {
83 IPython.notebook.move_cell_up();
91 that.notebook.move_cell_up();
84 92 }
85 93 },
86 94 {
87 95 id : 'move_down_b',
88 96 label : 'Move Cell Down',
89 icon : 'icon-arrow-down',
97 icon : 'fa-arrow-down',
90 98 callback : function () {
91 IPython.notebook.move_cell_down();
99 that.notebook.move_cell_down();
92 100 }
93 101 }
94 102 ],'move_up_down');
@@ -98,26 +106,26 b' var IPython = (function (IPython) {'
98 106 {
99 107 id : 'run_b',
100 108 label : 'Run Cell',
101 icon : 'icon-play',
109 icon : 'fa-play',
102 110 callback : function () {
103 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 116 id : 'interrupt_b',
109 117 label : 'Interrupt',
110 icon : 'icon-stop',
118 icon : 'fa-stop',
111 119 callback : function () {
112 IPython.notebook.session.interrupt_kernel();
120 that.notebook.session.interrupt_kernel();
113 121 }
114 122 },
115 123 {
116 124 id : 'repeat_b',
117 125 label : 'Restart Kernel',
118 icon : 'icon-repeat',
126 icon : 'fa-repeat',
119 127 callback : function () {
120 IPython.notebook.restart_kernel();
128 that.notebook.restart_kernel();
121 129 }
122 130 }
123 131 ],'run_int');
@@ -150,30 +158,31 b' var IPython = (function (IPython) {'
150 158 .addClass('form-control select-xs')
151 159 .append($('<option/>').attr('value', '').text('None'));
152 160 this.element.append(label).append(select);
161 var that = this;
153 162 select.change(function() {
154 var val = $(this).val()
155 if (val =='') {
156 IPython.CellToolbar.global_hide();
157 delete IPython.notebook.metadata.celltoolbar;
163 var val = $(this).val();
164 if (val ==='') {
165 celltoolbar.CellToolbar.global_hide();
166 delete that.notebook.metadata.celltoolbar;
158 167 } else {
159 IPython.CellToolbar.global_show();
160 IPython.CellToolbar.activate_preset(val);
161 IPython.notebook.metadata.celltoolbar = val;
168 celltoolbar.CellToolbar.global_show();
169 celltoolbar.CellToolbar.activate_preset(val, that.events);
170 that.notebook.metadata.celltoolbar = val;
162 171 }
163 172 });
164 173 // Setup the currently registered presets.
165 var presets = IPython.CellToolbar.list_presets();
174 var presets = celltoolbar.CellToolbar.list_presets();
166 175 for (var i=0; i<presets.length; i++) {
167 176 var name = presets[i];
168 177 select.append($('<option/>').attr('value', name).text(name));
169 178 }
170 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 181 var name = data.name;
173 182 select.append($('<option/>').attr('value', name).text(name));
174 183 });
175 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 186 if (select.val() !== data.name)
178 187 select.val(data.name);
179 188 });
@@ -186,26 +195,26 b' var IPython = (function (IPython) {'
186 195 this.element.find('#cell_type').change(function () {
187 196 var cell_type = $(this).val();
188 197 if (cell_type === 'code') {
189 IPython.notebook.to_code();
198 that.notebook.to_code();
190 199 } else if (cell_type === 'markdown') {
191 IPython.notebook.to_markdown();
200 that.notebook.to_markdown();
192 201 } else if (cell_type === 'raw') {
193 IPython.notebook.to_raw();
202 that.notebook.to_raw();
194 203 } else if (cell_type === 'heading1') {
195 IPython.notebook.to_heading(undefined, 1);
204 that.notebook.to_heading(undefined, 1);
196 205 } else if (cell_type === 'heading2') {
197 IPython.notebook.to_heading(undefined, 2);
206 that.notebook.to_heading(undefined, 2);
198 207 } else if (cell_type === 'heading3') {
199 IPython.notebook.to_heading(undefined, 3);
208 that.notebook.to_heading(undefined, 3);
200 209 } else if (cell_type === 'heading4') {
201 IPython.notebook.to_heading(undefined, 4);
210 that.notebook.to_heading(undefined, 4);
202 211 } else if (cell_type === 'heading5') {
203 IPython.notebook.to_heading(undefined, 5);
212 that.notebook.to_heading(undefined, 5);
204 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 218 if (data.cell_type === 'heading') {
210 219 that.element.find('#cell_type').val(data.cell_type+data.level);
211 220 } else {
@@ -214,8 +223,8 b' var IPython = (function (IPython) {'
214 223 });
215 224 };
216 225
226 // Backwards compatability.
217 227 IPython.MainToolBar = MainToolBar;
218 228
219 return IPython;
220
221 }(IPython));
229 return {'MainToolBar': MainToolBar};
230 });
@@ -1,18 +1,12 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
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 // MathJax utility functions
10 //============================================================================
11
12
13 IPython.namespace('IPython.mathjaxutils');
14
15 IPython.mathjaxutils = (function (IPython) {
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 'base/js/dialog',
9 ], function(IPython, $, utils, dialog) {
16 10 "use strict";
17 11
18 12 var init = function () {
@@ -75,7 +69,7 b' IPython.mathjaxutils = (function (IPython) {'
75 69 "which will prevent this dialog from appearing."
76 70 )
77 71 );
78 IPython.dialog.modal({
72 dialog.modal({
79 73 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
80 74 body : message,
81 75 buttons : {
@@ -110,7 +104,7 b' IPython.mathjaxutils = (function (IPython) {'
110 104 .replace(/</g, "&lt;") // use HTML entity for <
111 105 .replace(/>/g, "&gt;") // use HTML entity for >
112 106 ;
113 if (IPython.utils.browser === 'msie') {
107 if (utils.browser === 'msie') {
114 108 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
115 109 }
116 110 while (j > i) {
@@ -159,7 +153,7 b' IPython.mathjaxutils = (function (IPython) {'
159 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 158 for (var i = 1, m = blocks.length; i < m; i += 2) {
165 159 var block = blocks[i];
@@ -242,10 +236,13 b' IPython.mathjaxutils = (function (IPython) {'
242 236 return text;
243 237 };
244 238
245 return {
239 var mathjaxutils = {
246 240 init : init,
247 241 remove_math : remove_math,
248 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 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 //============================================================================
5 // MenuBar
6 //============================================================================
7
8 /**
9 * @module IPython
10 * @namespace IPython
11 * @submodule MenuBar
12 */
13
14
15 var IPython = (function (IPython) {
4 define([
5 'base/js/namespace',
6 'jquery',
7 'base/js/utils',
8 'notebook/js/tour',
9 'bootstrap',
10 'moment',
11 ], function(IPython, $, utils, tour, bootstrap, moment) {
16 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 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 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 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 47 if (this.selector !== undefined) {
39 48 this.element = $(selector);
40 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 55 MenuBar.prototype.style = function () {
46 this.element.addClass('border-box-sizing');
56 var that = this;
47 57 this.element.find("li").click(function (event, ui) {
48 58 // The selected cell loses focus when the menu is entered, so we
49 59 // re-select it upon selection.
50 var i = IPython.notebook.get_selected_index();
51 IPython.notebook.select(i);
60 var i = that.notebook.get_selected_index();
61 that.notebook.select(i);
52 62 }
53 63 );
54 64 };
55 65
56 66 MenuBar.prototype._nbconvert = function (format, download) {
57 67 download = download || false;
58 var notebook_path = IPython.notebook.notebook_path;
59 var notebook_name = IPython.notebook.notebook_name;
60 if (IPython.notebook.dirty) {
61 IPython.notebook.save_notebook({async : false});
68 var notebook_path = this.notebook.notebook_path;
69 var notebook_name = this.notebook.notebook_name;
70 if (this.notebook.dirty) {
71 this.notebook.save_notebook({async : false});
62 72 }
63 73 var url = utils.url_join_encode(
64 74 this.base_url,
@@ -75,25 +85,25 b' var IPython = (function (IPython) {'
75 85 // File
76 86 var that = this;
77 87 this.element.find('#new_notebook').click(function () {
78 IPython.notebook.new_notebook();
88 that.notebook.new_notebook();
79 89 });
80 90 this.element.find('#open_notebook').click(function () {
81 91 window.open(utils.url_join_encode(
82 IPython.notebook.base_url,
92 that.notebook.base_url,
83 93 'tree',
84 IPython.notebook.notebook_path
94 that.notebook.notebook_path
85 95 ));
86 96 });
87 97 this.element.find('#copy_notebook').click(function () {
88 IPython.notebook.copy_notebook();
98 that.notebook.copy_notebook();
89 99 return false;
90 100 });
91 101 this.element.find('#download_ipynb').click(function () {
92 var base_url = IPython.notebook.base_url;
93 var notebook_path = IPython.notebook.notebook_path;
94 var notebook_name = IPython.notebook.notebook_name;
95 if (IPython.notebook.dirty) {
96 IPython.notebook.save_notebook({async : false});
102 var base_url = that.notebook.base_url;
103 var notebook_path = that.notebook.notebook_path;
104 var notebook_name = that.notebook.notebook_name;
105 if (that.notebook.dirty) {
106 that.notebook.save_notebook({async : false});
97 107 }
98 108
99 109 var url = utils.url_join_encode(
@@ -126,17 +136,17 b' var IPython = (function (IPython) {'
126 136 });
127 137
128 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 141 this.element.find('#save_checkpoint').click(function () {
132 IPython.notebook.save_checkpoint();
142 that.notebook.save_checkpoint();
133 143 });
134 144 this.element.find('#restore_checkpoint').click(function () {
135 145 });
136 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 150 if (trusted) {
141 151 that.element.find('#trust_notebook')
142 152 .addClass("disabled")
@@ -148,157 +158,160 b' var IPython = (function (IPython) {'
148 158 }
149 159 });
150 160 this.element.find('#kill_and_exit').click(function () {
151 IPython.notebook.session.delete();
152 setTimeout(function(){
161 var close_window = function () {
153 162 // allow closing of new tabs in Chromium, impossible in FF
154 163 window.open('', '_self', '');
155 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 169 // Edit
159 170 this.element.find('#cut_cell').click(function () {
160 IPython.notebook.cut_cell();
171 that.notebook.cut_cell();
161 172 });
162 173 this.element.find('#copy_cell').click(function () {
163 IPython.notebook.copy_cell();
174 that.notebook.copy_cell();
164 175 });
165 176 this.element.find('#delete_cell').click(function () {
166 IPython.notebook.delete_cell();
177 that.notebook.delete_cell();
167 178 });
168 179 this.element.find('#undelete_cell').click(function () {
169 IPython.notebook.undelete_cell();
180 that.notebook.undelete_cell();
170 181 });
171 182 this.element.find('#split_cell').click(function () {
172 IPython.notebook.split_cell();
183 that.notebook.split_cell();
173 184 });
174 185 this.element.find('#merge_cell_above').click(function () {
175 IPython.notebook.merge_cell_above();
186 that.notebook.merge_cell_above();
176 187 });
177 188 this.element.find('#merge_cell_below').click(function () {
178 IPython.notebook.merge_cell_below();
189 that.notebook.merge_cell_below();
179 190 });
180 191 this.element.find('#move_cell_up').click(function () {
181 IPython.notebook.move_cell_up();
192 that.notebook.move_cell_up();
182 193 });
183 194 this.element.find('#move_cell_down').click(function () {
184 IPython.notebook.move_cell_down();
195 that.notebook.move_cell_down();
185 196 });
186 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 203 // View
191 204 this.element.find('#toggle_header').click(function () {
192 205 $('div#header').toggle();
193 IPython.layout_manager.do_resize();
206 that.layout_manager.do_resize();
194 207 });
195 208 this.element.find('#toggle_toolbar').click(function () {
196 209 $('div#maintoolbar').toggle();
197 IPython.layout_manager.do_resize();
210 that.layout_manager.do_resize();
198 211 });
199 212 // Insert
200 213 this.element.find('#insert_cell_above').click(function () {
201 IPython.notebook.insert_cell_above('code');
202 IPython.notebook.select_prev();
214 that.notebook.insert_cell_above('code');
215 that.notebook.select_prev();
203 216 });
204 217 this.element.find('#insert_cell_below').click(function () {
205 IPython.notebook.insert_cell_below('code');
206 IPython.notebook.select_next();
218 that.notebook.insert_cell_below('code');
219 that.notebook.select_next();
207 220 });
208 221 // Cell
209 222 this.element.find('#run_cell').click(function () {
210 IPython.notebook.execute_cell();
223 that.notebook.execute_cell();
211 224 });
212 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 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 231 this.element.find('#run_all_cells').click(function () {
219 IPython.notebook.execute_all_cells();
232 that.notebook.execute_all_cells();
220 233 });
221 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 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 240 this.element.find('#to_code').click(function () {
228 IPython.notebook.to_code();
241 that.notebook.to_code();
229 242 });
230 243 this.element.find('#to_markdown').click(function () {
231 IPython.notebook.to_markdown();
244 that.notebook.to_markdown();
232 245 });
233 246 this.element.find('#to_raw').click(function () {
234 IPython.notebook.to_raw();
247 that.notebook.to_raw();
235 248 });
236 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 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 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 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 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 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 268 this.element.find('#toggle_current_output').click(function () {
256 IPython.notebook.toggle_output();
269 that.notebook.toggle_output();
257 270 });
258 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 274 this.element.find('#clear_current_output').click(function () {
262 IPython.notebook.clear_output();
275 that.notebook.clear_output();
263 276 });
264 277
265 278 this.element.find('#toggle_all_output').click(function () {
266 IPython.notebook.toggle_all_output();
279 that.notebook.toggle_all_output();
267 280 });
268 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 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 288 // Kernel
276 289 this.element.find('#int_kernel').click(function () {
277 IPython.notebook.session.interrupt_kernel();
290 that.notebook.session.interrupt_kernel();
278 291 });
279 292 this.element.find('#restart_kernel').click(function () {
280 IPython.notebook.restart_kernel();
293 that.notebook.restart_kernel();
281 294 });
282 295 // Help
283 if (IPython.tour) {
296 if (this.tour) {
284 297 this.element.find('#notebook_tour').click(function () {
285 IPython.tour.start();
298 that.tour.start();
286 299 });
287 300 } else {
288 301 this.element.find('#notebook_tour').addClass("disabled");
289 302 }
290 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 307 this.update_restore_checkpoint(null);
295 308
296 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
297 that.update_restore_checkpoint(IPython.notebook.checkpoints);
309 this.events.on('checkpoints_listed.Notebook', function (event, data) {
310 that.update_restore_checkpoint(that.notebook.checkpoints);
298 311 });
299 312
300 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
301 that.update_restore_checkpoint(IPython.notebook.checkpoints);
313 this.events.on('checkpoint_created.Notebook', function (event, data) {
314 that.update_restore_checkpoint(that.notebook.checkpoints);
302 315 });
303 316 };
304 317
@@ -317,23 +330,24 b' var IPython = (function (IPython) {'
317 330 return;
318 331 }
319 332
333 var that = this;
320 334 checkpoints.map(function (checkpoint) {
321 335 var d = new Date(checkpoint.last_modified);
322 336 ul.append(
323 337 $("<li/>").append(
324 338 $("<a/>")
325 339 .attr("href", "#")
326 .text(d.format("mmm dd HH:MM:ss"))
340 .text(moment(d).format("LLLL"))
327 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 350 IPython.MenuBar = MenuBar;
336 351
337 return IPython;
338
339 }(IPython));
352 return {'MenuBar': MenuBar};
353 });
This diff has been collapsed as it changes many lines, (526 lines changed) Show them Hide them
@@ -1,32 +1,99 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
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 '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 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 57 this.base_url = options.base_url;
28 58 this.notebook_path = options.notebook_path;
29 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 97 this.element = $(selector);
31 98 this.element.scroll();
32 99 this.element.data("notebook", this);
@@ -55,23 +122,20 b' var IPython = (function (IPython) {'
55 122 this.notebook_name_blacklist_re = /[\/\\:]/;
56 123 this.nbformat = 3; // Increment this when changing the nbformat
57 124 this.nbformat_minor = 0; // Increment this when changing the nbformat
58 this.style();
125 this.codemirror_mode = 'ipython';
59 126 this.create_elements();
60 127 this.bind_events();
61 128 this.save_notebook = function() { // don't allow save until notebook_loaded
62 129 this.save_notebook_error(null, null, "Load failed, save is disabled");
63 130 };
64 };
65 131
66 /**
67 * Tweak the notebook's CSS style.
68 *
69 * @method style
70 */
71 Notebook.prototype.style = function () {
72 $('div#notebook').addClass('border-box-sizing');
132 // Trigger cell toolbar registration.
133 default_celltoolbar.register(this);
134 rawcell_celltoolbar.register(this);
135 slideshow_celltoolbar.register(this);
73 136 };
74 137
138
75 139 /**
76 140 * Create an HTML and CSS representation of the notebook.
77 141 *
@@ -102,36 +166,38 b' var IPython = (function (IPython) {'
102 166 Notebook.prototype.bind_events = function () {
103 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 170 var index = that.find_cell_index(data.cell);
107 171 var new_cell = that.insert_cell_below('code',index);
108 172 new_cell.set_text(data.text);
109 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 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 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 185 var index = that.find_cell_index(data.cell);
122 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 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 194 that.handle_command_mode(data.cell);
131 195 });
132 196
133 $([IPython.events]).on('status_autorestarting.Kernel', function () {
134 IPython.dialog.modal({
197 this.events.on('status_autorestarting.Kernel', function () {
198 dialog.modal({
199 notebook: that,
200 keyboard_manager: that.keyboard_manager,
135 201 title: "Kernel Restarting",
136 202 body: "The kernel appears to have died. It will restart automatically.",
137 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 218 var collapse_time = function (time) {
146 219 var app_height = $('#ipython-main-app').height(); // content height
@@ -211,7 +284,7 b' var IPython = (function (IPython) {'
211 284 if (this.dirty == value) {
212 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 328 Notebook.prototype.edit_metadata = function () {
256 329 var that = this;
257 IPython.dialog.edit_metadata(this.metadata, function (md) {
258 that.metadata = md;
259 }, 'Notebook');
330 dialog.edit_metadata({
331 md: this.metadata,
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 350 // Cell indexing, retrieval, etc.
263 351
@@ -295,7 +383,7 b' var IPython = (function (IPython) {'
295 383 * @return {Cell} Cell or null if no cell was found.
296 384 */
297 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 562 var cell = this.get_cell(index);
475 563 cell.select();
476 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 566 {'cell_type':cell.cell_type,level:cell.level}
479 567 );
480 568 } else {
481 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
569 this.events.trigger('selected_cell_type_changed.Notebook',
482 570 {'cell_type':cell.cell_type}
483 571 );
484 572 }
@@ -540,8 +628,8 b' var IPython = (function (IPython) {'
540 628 if (this.mode !== 'command') {
541 629 cell.command_mode();
542 630 this.mode = 'command';
543 $([IPython.events]).trigger('command_mode.Notebook');
544 IPython.keyboard_manager.command_mode();
631 this.events.trigger('command_mode.Notebook');
632 this.keyboard_manager.command_mode();
545 633 }
546 634 };
547 635
@@ -570,8 +658,8 b' var IPython = (function (IPython) {'
570 658 if (cell && this.mode !== 'edit') {
571 659 cell.edit_mode();
572 660 this.mode = 'edit';
573 $([IPython.events]).trigger('edit_mode.Notebook');
574 IPython.keyboard_manager.edit_mode();
661 this.events.trigger('edit_mode.Notebook');
662 this.keyboard_manager.edit_mode();
575 663 }
576 664 };
577 665
@@ -686,7 +774,7 b' var IPython = (function (IPython) {'
686 774 this.undelete_index = i;
687 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 778 this.set_dirty(true);
691 779 }
692 780 return this;
@@ -753,20 +841,27 b' var IPython = (function (IPython) {'
753 841 type = type || this.get_selected_cell().cell_type;
754 842
755 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 851 if (type === 'code') {
757 cell = new IPython.CodeCell(this.kernel);
852 cell = new codecell.CodeCell(this.kernel, cell_options);
758 853 cell.set_input_prompt();
759 854 } else if (type === 'markdown') {
760 cell = new IPython.MarkdownCell();
855 cell = new textcell.MarkdownCell(cell_options);
761 856 } else if (type === 'raw') {
762 cell = new IPython.RawCell();
857 cell = new textcell.RawCell(cell_options);
763 858 } else if (type === 'heading') {
764 cell = new IPython.HeadingCell();
859 cell = new textcell.HeadingCell(cell_options);
765 860 }
766 861
767 862 if(this._insert_element_at_index(cell.element,index)) {
768 863 cell.render();
769 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
864 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
770 865 cell.refresh();
771 866 // We used to select the cell after we refresh it, but there
772 867 // are now cases were this method is called where select is
@@ -876,7 +971,7 b' var IPython = (function (IPython) {'
876 971 if (this.is_valid_cell_index(i)) {
877 972 var source_element = this.get_cell_element(i);
878 973 var source_cell = source_element.data("cell");
879 if (!(source_cell instanceof IPython.CodeCell)) {
974 if (!(source_cell instanceof codecell.CodeCell)) {
880 975 var target_cell = this.insert_cell_below('code',i);
881 976 var text = source_cell.get_text();
882 977 if (text === source_cell.placeholder) {
@@ -906,7 +1001,7 b' var IPython = (function (IPython) {'
906 1001 if (this.is_valid_cell_index(i)) {
907 1002 var source_element = this.get_cell_element(i);
908 1003 var source_cell = source_element.data("cell");
909 if (!(source_cell instanceof IPython.MarkdownCell)) {
1004 if (!(source_cell instanceof textcell.MarkdownCell)) {
910 1005 var target_cell = this.insert_cell_below('markdown',i);
911 1006 var text = source_cell.get_text();
912 1007 if (text === source_cell.placeholder) {
@@ -920,7 +1015,7 b' var IPython = (function (IPython) {'
920 1015 target_cell.code_mirror.clearHistory();
921 1016 source_element.remove();
922 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 1019 target_cell.render();
925 1020 }
926 1021 var cursor = source_cell.code_mirror.getCursor();
@@ -942,7 +1037,7 b' var IPython = (function (IPython) {'
942 1037 var source_element = this.get_cell_element(i);
943 1038 var source_cell = source_element.data("cell");
944 1039 var target_cell = null;
945 if (!(source_cell instanceof IPython.RawCell)) {
1040 if (!(source_cell instanceof textcell.RawCell)) {
946 1041 target_cell = this.insert_cell_below('raw',i);
947 1042 var text = source_cell.get_text();
948 1043 if (text === source_cell.placeholder) {
@@ -977,7 +1072,7 b' var IPython = (function (IPython) {'
977 1072 var source_element = this.get_cell_element(i);
978 1073 var source_cell = source_element.data("cell");
979 1074 var target_cell = null;
980 if (source_cell instanceof IPython.HeadingCell) {
1075 if (source_cell instanceof textcell.HeadingCell) {
981 1076 source_cell.set_level(level);
982 1077 } else {
983 1078 target_cell = this.insert_cell_below('heading',i);
@@ -996,12 +1091,12 b' var IPython = (function (IPython) {'
996 1091 this.select(i);
997 1092 var cursor = source_cell.code_mirror.getCursor();
998 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 1095 target_cell.render();
1001 1096 }
1002 1097 }
1003 1098 this.set_dirty(true);
1004 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1099 this.events.trigger('selected_cell_type_changed.Notebook',
1005 1100 {'cell_type':'heading',level:level}
1006 1101 );
1007 1102 }
@@ -1115,26 +1210,17 b' var IPython = (function (IPython) {'
1115 1210 * @method split_cell
1116 1211 */
1117 1212 Notebook.prototype.split_cell = function () {
1118 var mdc = IPython.MarkdownCell;
1119 var rc = IPython.RawCell;
1213 var mdc = textcell.MarkdownCell;
1214 var rc = textcell.RawCell;
1120 1215 var cell = this.get_selected_cell();
1121 1216 if (cell.is_splittable()) {
1122 1217 var texta = cell.get_pre_cursor();
1123 1218 var textb = cell.get_post_cursor();
1124 if (cell instanceof IPython.CodeCell) {
1125 // In this case the operations keep the notebook in its existing mode
1126 // so we don't need to do any post-op mode changes.
1127 cell.set_text(textb);
1128 var new_cell = this.insert_cell_above('code');
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 }
1219 cell.set_text(textb);
1220 var new_cell = this.insert_cell_above(cell.cell_type);
1221 // Unrender the new cell so we can call set_text.
1222 new_cell.unrender();
1223 new_cell.set_text(texta);
1138 1224 }
1139 1225 };
1140 1226
@@ -1144,8 +1230,8 b' var IPython = (function (IPython) {'
1144 1230 * @method merge_cell_above
1145 1231 */
1146 1232 Notebook.prototype.merge_cell_above = function () {
1147 var mdc = IPython.MarkdownCell;
1148 var rc = IPython.RawCell;
1233 var mdc = textcell.MarkdownCell;
1234 var rc = textcell.RawCell;
1149 1235 var index = this.get_selected_index();
1150 1236 var cell = this.get_cell(index);
1151 1237 var render = cell.rendered;
@@ -1159,9 +1245,9 b' var IPython = (function (IPython) {'
1159 1245 }
1160 1246 var upper_text = upper_cell.get_text();
1161 1247 var text = cell.get_text();
1162 if (cell instanceof IPython.CodeCell) {
1248 if (cell instanceof codecell.CodeCell) {
1163 1249 cell.set_text(upper_text+'\n'+text);
1164 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1250 } else {
1165 1251 cell.unrender(); // Must unrender before we set_text.
1166 1252 cell.set_text(upper_text+'\n\n'+text);
1167 1253 if (render) {
@@ -1181,8 +1267,8 b' var IPython = (function (IPython) {'
1181 1267 * @method merge_cell_below
1182 1268 */
1183 1269 Notebook.prototype.merge_cell_below = function () {
1184 var mdc = IPython.MarkdownCell;
1185 var rc = IPython.RawCell;
1270 var mdc = textcell.MarkdownCell;
1271 var rc = textcell.RawCell;
1186 1272 var index = this.get_selected_index();
1187 1273 var cell = this.get_cell(index);
1188 1274 var render = cell.rendered;
@@ -1196,9 +1282,9 b' var IPython = (function (IPython) {'
1196 1282 }
1197 1283 var lower_text = lower_cell.get_text();
1198 1284 var text = cell.get_text();
1199 if (cell instanceof IPython.CodeCell) {
1285 if (cell instanceof codecell.CodeCell) {
1200 1286 cell.set_text(text+'\n'+lower_text);
1201 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1287 } else {
1202 1288 cell.unrender(); // Must unrender before we set_text.
1203 1289 cell.set_text(text+'\n\n'+lower_text);
1204 1290 if (render) {
@@ -1224,7 +1310,7 b' var IPython = (function (IPython) {'
1224 1310 Notebook.prototype.collapse_output = function (index) {
1225 1311 var i = this.index_or_selected(index);
1226 1312 var cell = this.get_cell(i);
1227 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1313 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1228 1314 cell.collapse_output();
1229 1315 this.set_dirty(true);
1230 1316 }
@@ -1237,7 +1323,7 b' var IPython = (function (IPython) {'
1237 1323 */
1238 1324 Notebook.prototype.collapse_all_output = function () {
1239 1325 $.map(this.get_cells(), function (cell, i) {
1240 if (cell instanceof IPython.CodeCell) {
1326 if (cell instanceof codecell.CodeCell) {
1241 1327 cell.collapse_output();
1242 1328 }
1243 1329 });
@@ -1254,7 +1340,7 b' var IPython = (function (IPython) {'
1254 1340 Notebook.prototype.expand_output = function (index) {
1255 1341 var i = this.index_or_selected(index);
1256 1342 var cell = this.get_cell(i);
1257 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1343 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1258 1344 cell.expand_output();
1259 1345 this.set_dirty(true);
1260 1346 }
@@ -1267,7 +1353,7 b' var IPython = (function (IPython) {'
1267 1353 */
1268 1354 Notebook.prototype.expand_all_output = function () {
1269 1355 $.map(this.get_cells(), function (cell, i) {
1270 if (cell instanceof IPython.CodeCell) {
1356 if (cell instanceof codecell.CodeCell) {
1271 1357 cell.expand_output();
1272 1358 }
1273 1359 });
@@ -1284,7 +1370,7 b' var IPython = (function (IPython) {'
1284 1370 Notebook.prototype.clear_output = function (index) {
1285 1371 var i = this.index_or_selected(index);
1286 1372 var cell = this.get_cell(i);
1287 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1373 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1288 1374 cell.clear_output();
1289 1375 this.set_dirty(true);
1290 1376 }
@@ -1297,7 +1383,7 b' var IPython = (function (IPython) {'
1297 1383 */
1298 1384 Notebook.prototype.clear_all_output = function () {
1299 1385 $.map(this.get_cells(), function (cell, i) {
1300 if (cell instanceof IPython.CodeCell) {
1386 if (cell instanceof codecell.CodeCell) {
1301 1387 cell.clear_output();
1302 1388 }
1303 1389 });
@@ -1313,7 +1399,7 b' var IPython = (function (IPython) {'
1313 1399 Notebook.prototype.scroll_output = function (index) {
1314 1400 var i = this.index_or_selected(index);
1315 1401 var cell = this.get_cell(i);
1316 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1402 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1317 1403 cell.scroll_output();
1318 1404 this.set_dirty(true);
1319 1405 }
@@ -1326,7 +1412,7 b' var IPython = (function (IPython) {'
1326 1412 */
1327 1413 Notebook.prototype.scroll_all_output = function () {
1328 1414 $.map(this.get_cells(), function (cell, i) {
1329 if (cell instanceof IPython.CodeCell) {
1415 if (cell instanceof codecell.CodeCell) {
1330 1416 cell.scroll_output();
1331 1417 }
1332 1418 });
@@ -1342,7 +1428,7 b' var IPython = (function (IPython) {'
1342 1428 Notebook.prototype.toggle_output = function (index) {
1343 1429 var i = this.index_or_selected(index);
1344 1430 var cell = this.get_cell(i);
1345 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1431 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1346 1432 cell.toggle_output();
1347 1433 this.set_dirty(true);
1348 1434 }
@@ -1355,7 +1441,7 b' var IPython = (function (IPython) {'
1355 1441 */
1356 1442 Notebook.prototype.toggle_all_output = function () {
1357 1443 $.map(this.get_cells(), function (cell, i) {
1358 if (cell instanceof IPython.CodeCell) {
1444 if (cell instanceof codecell.CodeCell) {
1359 1445 cell.toggle_output();
1360 1446 }
1361 1447 });
@@ -1372,7 +1458,7 b' var IPython = (function (IPython) {'
1372 1458 Notebook.prototype.toggle_output_scroll = function (index) {
1373 1459 var i = this.index_or_selected(index);
1374 1460 var cell = this.get_cell(i);
1375 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1461 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1376 1462 cell.toggle_output_scroll();
1377 1463 this.set_dirty(true);
1378 1464 }
@@ -1385,7 +1471,7 b' var IPython = (function (IPython) {'
1385 1471 */
1386 1472 Notebook.prototype.toggle_all_output_scroll = function () {
1387 1473 $.map(this.get_cells(), function (cell, i) {
1388 if (cell instanceof IPython.CodeCell) {
1474 if (cell instanceof codecell.CodeCell) {
1389 1475 cell.toggle_output_scroll();
1390 1476 }
1391 1477 });
@@ -1403,6 +1489,34 b' var IPython = (function (IPython) {'
1403 1489 Notebook.prototype.cell_toggle_line_numbers = function() {
1404 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 1521 // Session related things
1408 1522
@@ -1411,9 +1525,54 b' var IPython = (function (IPython) {'
1411 1525 *
1412 1526 * @method start_session
1413 1527 */
1414 Notebook.prototype.start_session = function () {
1415 this.session = new IPython.Session(this, this.options);
1416 this.session.start($.proxy(this._session_started, this));
1528 Notebook.prototype.start_session = function (kernel_name) {
1529 var that = 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 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 1586 this.kernel = this.session.kernel;
1427 1587 var ncells = this.ncells();
1428 1588 for (var i=0; i<ncells; i++) {
1429 1589 var cell = this.get_cell(i);
1430 if (cell instanceof IPython.CodeCell) {
1590 if (cell instanceof codecell.CodeCell) {
1431 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 1601 * Prompt the user to restart the IPython kernel.
1438 1602 *
@@ -1440,7 +1604,9 b' var IPython = (function (IPython) {'
1440 1604 */
1441 1605 Notebook.prototype.restart_kernel = function () {
1442 1606 var that = this;
1443 IPython.dialog.modal({
1607 dialog.modal({
1608 notebook: this,
1609 keyboard_manager: this.keyboard_manager,
1444 1610 title : "Restart kernel or continue running?",
1445 1611 body : $("<p/>").text(
1446 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 1799 this.metadata = content.metadata;
1634 1800 this.notebook_name = data.name;
1635 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 1809 // Only handle 1 worksheet for now.
1637 1810 var worksheet = content.worksheets[0];
1638 1811 if (worksheet !== undefined) {
@@ -1660,10 +1833,12 b' var IPython = (function (IPython) {'
1660 1833 }
1661 1834 if (trusted != this.trusted) {
1662 1835 this.trusted = trusted;
1663 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1836 this.events.trigger("trust_changed.Notebook", trusted);
1664 1837 }
1665 1838 if (content.worksheets.length > 1) {
1666 IPython.dialog.modal({
1839 dialog.modal({
1840 notebook: this,
1841 keyboard_manager: this.keyboard_manager,
1667 1842 title : "Multiple worksheets",
1668 1843 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1669 1844 "but this version of IPython can only handle the first. " +
@@ -1705,7 +1880,7 b' var IPython = (function (IPython) {'
1705 1880 };
1706 1881 if (trusted != this.trusted) {
1707 1882 this.trusted = trusted;
1708 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1883 this.events.trigger("trust_changed.Notebook", trusted);
1709 1884 }
1710 1885 return data;
1711 1886 };
@@ -1730,10 +1905,10 b' var IPython = (function (IPython) {'
1730 1905 that.save_notebook();
1731 1906 }
1732 1907 }, interval);
1733 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1908 this.events.trigger("autosave_enabled.Notebook", interval);
1734 1909 } else {
1735 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 1923 var model = {};
1749 1924 model.name = this.notebook_name;
1750 1925 model.path = this.notebook_path;
1926 model.type = 'notebook';
1927 model.format = 'json';
1751 1928 model.content = this.toJSON();
1752 1929 model.content.nbformat = this.nbformat;
1753 1930 model.content.nbformat_minor = this.nbformat_minor;
@@ -1768,10 +1945,10 b' var IPython = (function (IPython) {'
1768 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 1949 var url = utils.url_join_encode(
1773 1950 this.base_url,
1774 'api/notebooks',
1951 'api/contents',
1775 1952 this.notebook_path,
1776 1953 this.notebook_name
1777 1954 );
@@ -1789,7 +1966,7 b' var IPython = (function (IPython) {'
1789 1966 */
1790 1967 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1791 1968 this.set_dirty(false);
1792 $([IPython.events]).trigger('notebook_saved.Notebook');
1969 this.events.trigger('notebook_saved.Notebook');
1793 1970 this._update_autosave_interval(start);
1794 1971 if (this._checkpoint_after_save) {
1795 1972 this.create_checkpoint();
@@ -1826,7 +2003,7 b' var IPython = (function (IPython) {'
1826 2003 * @param {String} error HTTP error message
1827 2004 */
1828 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 2030 var nb = this;
1854 IPython.dialog.modal({
2031 dialog.modal({
2032 notebook: this,
2033 keyboard_manager: this.keyboard_manager,
1855 2034 title: "Trust this notebook?",
1856 2035 body: body,
1857 2036
@@ -1867,7 +2046,7 b' var IPython = (function (IPython) {'
1867 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 2050 window.location.reload();
1872 2051 });
1873 2052 nb.save_notebook();
@@ -1902,7 +2081,7 b' var IPython = (function (IPython) {'
1902 2081 };
1903 2082 var url = utils.url_join_encode(
1904 2083 base_url,
1905 'api/notebooks',
2084 'api/contents',
1906 2085 path
1907 2086 );
1908 2087 $.ajax(url,settings);
@@ -1931,7 +2110,7 b' var IPython = (function (IPython) {'
1931 2110 };
1932 2111 var url = utils.url_join_encode(
1933 2112 base_url,
1934 'api/notebooks',
2113 'api/contents',
1935 2114 path
1936 2115 );
1937 2116 $.ajax(url,settings);
@@ -1953,10 +2132,10 b' var IPython = (function (IPython) {'
1953 2132 success : $.proxy(that.rename_success, this),
1954 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 2136 var url = utils.url_join_encode(
1958 2137 this.base_url,
1959 'api/notebooks',
2138 'api/contents',
1960 2139 this.notebook_path,
1961 2140 this.notebook_name
1962 2141 );
@@ -1974,7 +2153,7 b' var IPython = (function (IPython) {'
1974 2153 };
1975 2154 var url = utils.url_join_encode(
1976 2155 this.base_url,
1977 'api/notebooks',
2156 'api/contents',
1978 2157 this.notebook_path,
1979 2158 this.notebook_name
1980 2159 );
@@ -1986,32 +2165,33 b' var IPython = (function (IPython) {'
1986 2165 var name = this.notebook_name = json.name;
1987 2166 var path = json.path;
1988 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 2171 Notebook.prototype.rename_error = function (xhr, status, error) {
1993 2172 var that = this;
1994 var dialog = $('<div/>').append(
1995 $("<p/>").addClass("rename-message")
1996 .text('This notebook name already exists.')
2173 var dialog_body = $('<div/>').append(
2174 $("<p/>").text('This notebook name already exists.')
1997 2175 );
1998 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1999 IPython.dialog.modal({
2176 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2177 dialog.modal({
2178 notebook: this,
2179 keyboard_manager: this.keyboard_manager,
2000 2180 title: "Notebook Rename Error!",
2001 body: dialog,
2181 body: dialog_body,
2002 2182 buttons : {
2003 2183 "Cancel": {},
2004 2184 "OK": {
2005 2185 class: "btn-primary",
2006 2186 click: function () {
2007 IPython.save_widget.rename_notebook();
2187 this.save_widget.rename_notebook({notebook:that});
2008 2188 }}
2009 2189 },
2010 2190 open : function (event, ui) {
2011 2191 var that = $(this);
2012 2192 // Upon ENTER, click the OK button.
2013 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 2195 that.find('.btn-primary').first().click();
2016 2196 }
2017 2197 });
@@ -2039,10 +2219,10 b' var IPython = (function (IPython) {'
2039 2219 success : $.proxy(this.load_notebook_success,this),
2040 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 2223 var url = utils.url_join_encode(
2044 2224 this.base_url,
2045 'api/notebooks',
2225 'api/contents',
2046 2226 this.notebook_path,
2047 2227 this.notebook_name
2048 2228 );
@@ -2077,7 +2257,9 b' var IPython = (function (IPython) {'
2077 2257 "newer notebook format will be used and older versions of IPython " +
2078 2258 "may not be able to read it. To keep the older version, close the " +
2079 2259 "notebook without saving it.";
2080 IPython.dialog.modal({
2260 dialog.modal({
2261 notebook: this,
2262 keyboard_manager: this.keyboard_manager,
2081 2263 title : "Notebook converted",
2082 2264 body : msg,
2083 2265 buttons : {
@@ -2094,7 +2276,9 b' var IPython = (function (IPython) {'
2094 2276 this_vs + ". You can still work with this notebook, but some features " +
2095 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 2282 title : "Newer Notebook",
2099 2283 body : msg,
2100 2284 buttons : {
@@ -2109,22 +2293,25 b' var IPython = (function (IPython) {'
2109 2293 // Create the session after the notebook is completely loaded to prevent
2110 2294 // code execution upon loading, which is a security risk.
2111 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 2301 // load our checkpoint list
2115 2302 this.list_checkpoints();
2116 2303
2117 2304 // load toolbar state
2118 2305 if (this.metadata.celltoolbar) {
2119 IPython.CellToolbar.global_show();
2120 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2306 celltoolbar.CellToolbar.global_show();
2307 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2121 2308 } else {
2122 IPython.CellToolbar.global_hide();
2309 celltoolbar.CellToolbar.global_hide();
2123 2310 }
2124 2311
2125 2312 // now that we're fully loaded, it is safe to restore save functionality
2126 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 2323 * @param {String} error HTTP error message
2137 2324 */
2138 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 2328 var msg;
2141 2329 if (xhr.status === 400) {
2142 msg = error;
2330 msg = escape(utils.ajax_error_msg(xhr));
2143 2331 } else if (xhr.status === 500) {
2144 2332 msg = "An unknown error occurred while loading this notebook. " +
2145 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 2339 title: "Error loading notebook",
2150 2340 body : msg,
2151 2341 buttons : {
@@ -2196,7 +2386,7 b' var IPython = (function (IPython) {'
2196 2386 Notebook.prototype.list_checkpoints = function () {
2197 2387 var url = utils.url_join_encode(
2198 2388 this.base_url,
2199 'api/notebooks',
2389 'api/contents',
2200 2390 this.notebook_path,
2201 2391 this.notebook_name,
2202 2392 'checkpoints'
@@ -2224,7 +2414,7 b' var IPython = (function (IPython) {'
2224 2414 } else {
2225 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 2426 * @param {String} error_msg HTTP error message
2237 2427 */
2238 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 2437 Notebook.prototype.create_checkpoint = function () {
2248 2438 var url = utils.url_join_encode(
2249 2439 this.base_url,
2250 'api/notebooks',
2440 'api/contents',
2251 2441 this.notebook_path,
2252 2442 this.notebook_name,
2253 2443 'checkpoints'
@@ -2270,7 +2460,7 b' var IPython = (function (IPython) {'
2270 2460 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2271 2461 data = $.parseJSON(data);
2272 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 2472 * @param {String} error_msg HTTP error message
2283 2473 */
2284 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 2478 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
@@ -2309,7 +2499,9 b' var IPython = (function (IPython) {'
2309 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 2505 title : "Revert notebook to checkpoint",
2314 2506 body : body,
2315 2507 buttons : {
@@ -2331,10 +2523,10 b' var IPython = (function (IPython) {'
2331 2523 * @param {String} checkpoint ID
2332 2524 */
2333 2525 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2334 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2526 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2335 2527 var url = utils.url_join_encode(
2336 2528 this.base_url,
2337 'api/notebooks',
2529 'api/contents',
2338 2530 this.notebook_path,
2339 2531 this.notebook_name,
2340 2532 'checkpoints',
@@ -2356,7 +2548,7 b' var IPython = (function (IPython) {'
2356 2548 * @param {jqXHR} xhr jQuery Ajax object
2357 2549 */
2358 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 2552 this.load_notebook(this.notebook_name, this.notebook_path);
2361 2553 };
2362 2554
@@ -2369,7 +2561,7 b' var IPython = (function (IPython) {'
2369 2561 * @param {String} error_msg HTTP error message
2370 2562 */
2371 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 2571 * @param {String} checkpoint ID
2380 2572 */
2381 2573 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2382 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2574 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2383 2575 var url = utils.url_join_encode(
2384 2576 this.base_url,
2385 'api/notebooks',
2577 'api/contents',
2386 2578 this.notebook_path,
2387 2579 this.notebook_name,
2388 2580 'checkpoints',
@@ -2404,7 +2596,7 b' var IPython = (function (IPython) {'
2404 2596 * @param {jqXHR} xhr jQuery Ajax object
2405 2597 */
2406 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 2600 this.load_notebook(this.notebook_name, this.notebook_path);
2409 2601 };
2410 2602
@@ -2414,17 +2606,15 b' var IPython = (function (IPython) {'
2414 2606 * @method delete_checkpoint_error
2415 2607 * @param {jqXHR} xhr jQuery Ajax object
2416 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) {
2420 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2611 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
2612 this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
2421 2613 };
2422 2614
2423 2615
2616 // For backwards compatability.
2424 2617 IPython.Notebook = Notebook;
2425 2618
2426
2427 return IPython;
2428
2429 }(IPython));
2430
2619 return {'Notebook': Notebook};
2620 });
@@ -1,21 +1,31 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
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;
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
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 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 29 if (this.selector !== undefined) {
20 30 this.element = $(selector);
21 31 }
@@ -23,13 +33,10 b' var IPython = (function (IPython) {'
23 33 };
24 34
25 35 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
26 var uuid = utils.uuid();
27 36 if( css_class == 'danger') {css_class = 'ui-state-error';}
28 37 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
29 38 var tdiv = $('<div>')
30 .attr('id',uuid)
31 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
32 .addClass('border-box-sizing')
39 .addClass('notification_widget')
33 40 .addClass(css_class)
34 41 .hide()
35 42 .text(msg);
@@ -63,46 +70,53 b' var IPython = (function (IPython) {'
63 70 }
64 71 var div = $('<div/>').attr('id','notification_'+name);
65 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 74 return this.widget_dict[name];
68 75 };
69 76
70 77 NotificationArea.prototype.init_notification_widgets = function() {
78 var that = this;
71 79 var knw = this.new_notification_widget('kernel');
72 80 var $kernel_ind_icon = $("#kernel_indicator_icon");
73 81 var $modal_ind_icon = $("#modal_indicator_icon");
74 82
75 83 // Command/Edit mode
76 $([IPython.events]).on('edit_mode.Notebook',function () {
77 IPython.save_widget.update_document_title();
84 this.events.on('edit_mode.Notebook',function () {
85 that.save_widget.update_document_title();
78 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 () {
82 IPython.save_widget.update_document_title();
89 this.events.on('command_mode.Notebook',function () {
90 that.save_widget.update_document_title();
83 91 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
84 92 });
85 93
86 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 97 // Kernel events
90 $([IPython.events]).on('status_idle.Kernel',function () {
91 IPython.save_widget.update_document_title();
98 this.events.on('status_idle.Kernel',function () {
99 that.save_widget.update_document_title();
92 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 104 window.document.title='(Busy) '+window.document.title;
97 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 () {
101 IPython.save_widget.update_document_title();
108 this.events.on('status_restarting.Kernel',function () {
109 that.save_widget.update_document_title();
102 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 120 knw.set_message("Interrupting kernel", 2000);
107 121 });
108 122
@@ -110,28 +124,32 b' var IPython = (function (IPython) {'
110 124 // When the kernel_info reply arrives, the kernel is idle.
111 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 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 136 var msg = 'The kernel has died, and the automatic restart has failed.' +
121 137 ' It is possible the kernel cannot be restarted.' +
122 138 ' If you are not able to restart the kernel, you will still be able to save' +
123 139 ' the notebook, but running code will no longer work until the notebook' +
124 140 ' is reopened.';
125 141
126 IPython.dialog.modal({
142 dialog.modal({
127 143 title: "Dead kernel",
128 144 body : msg,
145 keyboard_manager: that.keyboard_manager,
146 notebook: that.notebook,
129 147 buttons : {
130 148 "Manual Restart": {
131 149 class: "btn-danger",
132 150 click: function () {
133 $([IPython.events]).trigger('status_restarting.Kernel');
134 IPython.notebook.start_kernel();
151 that.events.trigger('status_restarting.Kernel');
152 that.notebook.start_kernel();
135 153 }
136 154 },
137 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 161 var kernel = data.kernel;
144 162 var ws_url = data.ws_url;
145 163 var early = data.early;
146 164 var msg;
165
166 $kernel_ind_icon
167 .attr('class', 'kernel_disconnected_icon')
168 .attr('title', 'No Connection to Kernel');
169
147 170 if (!early) {
148 knw.set_message('Reconnecting WebSockets', 1000);
171 knw.warning('Reconnecting');
149 172 setTimeout(function () {
150 173 kernel.start_channels();
151 174 }, 5000);
@@ -155,14 +178,16 b' var IPython = (function (IPython) {'
155 178 msg = "A WebSocket connection could not be established." +
156 179 " You will NOT be able to run code. Check your" +
157 180 " network connection or notebook server configuration.";
158 IPython.dialog.modal({
181 dialog.modal({
159 182 title: "WebSocket connection failed",
160 183 body: msg,
184 keyboard_manager: that.keyboard_manager,
185 notebook: that.notebook,
161 186 buttons : {
162 187 "OK": {},
163 188 "Reconnect": {
164 189 click: function () {
165 knw.set_message('Reconnecting WebSockets', 1000);
190 knw.warning('Reconnecting');
166 191 setTimeout(function () {
167 192 kernel.start_channels();
168 193 }, 5000);
@@ -176,52 +201,52 b' var IPython = (function (IPython) {'
176 201 var nnw = this.new_notification_widget('notebook');
177 202
178 203 // Notebook events
179 $([IPython.events]).on('notebook_loading.Notebook', function () {
204 this.events.on('notebook_loading.Notebook', function () {
180 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 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 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 214 nnw.set_message("Notebook saved",2000);
190 215 });
191 $([IPython.events]).on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
192 nnw.set_message(data || "Notebook save failed");
216 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
217 nnw.warning(data || "Notebook save failed");
193 218 });
194 219
195 220 // Checkpoint events
196 $([IPython.events]).on('checkpoint_created.Notebook', function (evt, data) {
221 this.events.on('checkpoint_created.Notebook', function (evt, data) {
197 222 var msg = "Checkpoint created";
198 223 if (data.last_modified) {
199 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 227 nnw.set_message(msg, 2000);
203 228 });
204 $([IPython.events]).on('checkpoint_failed.Notebook', function () {
205 nnw.set_message("Checkpoint failed");
229 this.events.on('checkpoint_failed.Notebook', function () {
230 nnw.warning("Checkpoint failed");
206 231 });
207 $([IPython.events]).on('checkpoint_deleted.Notebook', function () {
232 this.events.on('checkpoint_deleted.Notebook', function () {
208 233 nnw.set_message("Checkpoint deleted", 500);
209 234 });
210 $([IPython.events]).on('checkpoint_delete_failed.Notebook', function () {
211 nnw.set_message("Checkpoint delete failed");
235 this.events.on('checkpoint_delete_failed.Notebook', function () {
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 239 nnw.set_message("Restoring to checkpoint...", 500);
215 240 });
216 $([IPython.events]).on('checkpoint_restore_failed.Notebook', function () {
217 nnw.set_message("Checkpoint restore failed");
241 this.events.on('checkpoint_restore_failed.Notebook', function () {
242 nnw.warning("Checkpoint restore failed");
218 243 });
219 244
220 245 // Autosave events
221 $([IPython.events]).on('autosave_disabled.Notebook', function () {
246 this.events.on('autosave_disabled.Notebook', function () {
222 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 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 255 IPython.NotificationArea = NotificationArea;
231 256
232 return IPython;
233
234 }(IPython));
235
257 return {'NotificationArea': NotificationArea};
258 });
@@ -1,18 +1,11 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
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 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
7 3
8 //============================================================================
9 // Notification widget
10 //============================================================================
11
12 var IPython = (function (IPython) {
4 define([
5 'base/js/namespace',
6 'jquery',
7 ], function(IPython, $) {
13 8 "use strict";
14 var utils = IPython.utils;
15
16 9
17 10 var NotificationWidget = function (selector) {
18 11 this.selector = selector;
@@ -22,7 +15,6 b' var IPython = (function (IPython) {'
22 15 this.element = $(selector);
23 16 this.style();
24 17 }
25 this.element.button();
26 18 this.element.hide();
27 19 var that = this;
28 20
@@ -31,10 +23,8 b' var IPython = (function (IPython) {'
31 23
32 24 };
33 25
34
35 26 NotificationWidget.prototype.style = function () {
36 this.element.addClass('notification_widget pull-right');
37 this.element.addClass('border-box-sizing');
27 this.element.addClass('notification_widget');
38 28 };
39 29
40 30 // msg : message to display
@@ -43,14 +33,24 b' var IPython = (function (IPython) {'
43 33 // if timeout <= 0
44 34 // click_callback : function called if user click on notification
45 35 // could return false to prevent the notification to be dismissed
46 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, opts) {
47 var opts = opts || {};
48 var callback = click_callback || function() {return false;};
36 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) {
37 var options = options || {};
38 var callback = click_callback || function() {return true;};
49 39 var that = this;
50 this.inner.attr('class', opts.icon);
51 this.inner.attr('title', opts.title);
40 // unbind potential previous callback
41 this.element.unbind('click');
42 this.inner.attr('class', options.icon);
43 this.inner.attr('title', options.title);
52 44 this.inner.text(msg);
53 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 54 if (this.timeout !== null) {
55 55 clearTimeout(this.timeout);
56 56 this.timeout = null;
@@ -62,7 +62,7 b' var IPython = (function (IPython) {'
62 62 }, timeout);
63 63 } else {
64 64 this.element.click(function() {
65 if( callback() != false ) {
65 if( callback() !== false ) {
66 66 that.element.fadeOut(100, function () {that.inner.text('');});
67 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 96 NotificationWidget.prototype.get_message = function () {
79 97 return this.inner.html();
80 98 };
81 99
82
100 // For backwards compatibility.
83 101 IPython.NotificationWidget = NotificationWidget;
84 102
85 return IPython;
86
87 }(IPython));
88
103 return {'NotificationWidget': NotificationWidget};
104 });
@@ -1,38 +1,37 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 //============================================================================
5 // OutputArea
6 //============================================================================
7
8 /**
9 * @module IPython
10 * @namespace IPython
11 * @submodule OutputArea
12 */
13 var IPython = (function (IPython) {
4 define([
5 'base/js/namespace',
6 'jqueryui',
7 'base/js/utils',
8 'base/js/security',
9 'base/js/keyboard',
10 'notebook/js/mathjaxutils',
11 'components/marked/lib/marked',
12 ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
14 13 "use strict";
15 14
16 var utils = IPython.utils;
17
18 15 /**
19 16 * @class OutputArea
20 17 *
21 18 * @constructor
22 19 */
23 20
24 var OutputArea = function (selector, prompt_area) {
25 this.selector = selector;
26 this.wrapper = $(selector);
21 var OutputArea = function (options) {
22 this.selector = options.selector;
23 this.events = options.events;
24 this.keyboard_manager = options.keyboard_manager;
25 this.wrapper = $(options.selector);
27 26 this.outputs = [];
28 27 this.collapsed = false;
29 28 this.scrolled = false;
30 29 this.trusted = true;
31 30 this.clear_queued = null;
32 if (prompt_area === undefined) {
31 if (options.prompt_area === undefined) {
33 32 this.prompt_area = true;
34 33 } else {
35 this.prompt_area = prompt_area;
34 this.prompt_area = options.prompt_area;
36 35 }
37 36 this.create_elements();
38 37 this.style();
@@ -101,7 +100,7 b' var IPython = (function (IPython) {'
101 100
102 101 this.element.resize(function () {
103 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 104 return;
106 105 }
107 106 // maybe scroll output,
@@ -282,12 +281,15 b' var IPython = (function (IPython) {'
282 281 needs_height_reset = true;
283 282 }
284 283
284 var record_output = true;
285
285 286 if (json.output_type === 'execute_result') {
286 287 this.append_execute_result(json);
287 288 } else if (json.output_type === 'error') {
288 289 this.append_error(json);
289 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 295 // We must release the animation fixed height in a callback since Gecko
@@ -308,7 +310,9 b' var IPython = (function (IPython) {'
308 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 463 // latest output was in the same stream,
460 464 // so append directly into its pre tag
461 465 // escape ANSI & HTML specials:
466 last.text = utils.fixCarriageReturn(last.text + json.text);
462 467 var pre = this.element.find('div.'+subclass).last().find('pre');
463 var html = utils.fixCarriageReturn(
464 pre.html() + utils.fixConsole(text));
468 var html = utils.fixConsole(last.text);
465 469 // The only user content injected with this HTML call is
466 470 // escaped by the fixConsole() method.
467 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 478 if (!text.replace("\r", "")) {
473 479 // text is nothing (empty string, \r, etc.)
474 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 485 // If we got here, attach a new div
@@ -482,6 +489,7 b' var IPython = (function (IPython) {'
482 489 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
483 490 }
484 491 this._safe_append(toinsert);
492 return true;
485 493 };
486 494
487 495
@@ -515,7 +523,7 b' var IPython = (function (IPython) {'
515 523 if (!this.trusted && !OutputArea.safe_outputs[type]) {
516 524 // not trusted, sanitize HTML
517 525 if (type==='text/html' || type==='text/svg') {
518 value = IPython.security.sanitize_html(value);
526 value = security.sanitize_html(value);
519 527 } else {
520 528 // don't display if we don't know how to sanitize it
521 529 console.log("Ignoring untrusted " + type + " output.");
@@ -531,7 +539,7 b' var IPython = (function (IPython) {'
531 539 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
532 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 543 return toinsert;
536 544 }
537 545 }
@@ -542,7 +550,7 b' var IPython = (function (IPython) {'
542 550 var append_html = function (html, md, element) {
543 551 var type = 'text/html';
544 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 554 toinsert.append(html);
547 555 element.append(toinsert);
548 556 return toinsert;
@@ -552,11 +560,11 b' var IPython = (function (IPython) {'
552 560 var append_markdown = function(markdown, md, element) {
553 561 var type = 'text/markdown';
554 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 564 var text = text_and_math[0];
557 565 var math = text_and_math[1];
558 566 var html = marked.parser(marked.lexer(text));
559 html = IPython.mathjaxutils.replace_math(html, math);
567 html = mathjaxutils.replace_math(html, math);
560 568 toinsert.append(html);
561 569 element.append(toinsert);
562 570 return toinsert;
@@ -567,13 +575,8 b' var IPython = (function (IPython) {'
567 575 // We just eval the JS code, element appears in the local scope.
568 576 var type = 'application/javascript';
569 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 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 581 // Fix for ipython/issues/5293, make sure `element` is the area which
579 582 // output can be inserted into at the time of JS execution.
@@ -763,7 +766,7 b' var IPython = (function (IPython) {'
763 766 .keydown(function (event, ui) {
764 767 // make sure we submit on enter,
765 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 770 that._submit_raw_input();
768 771 return false;
769 772 }
@@ -775,7 +778,7 b' var IPython = (function (IPython) {'
775 778 var raw_input = area.find('input.raw_input');
776 779 // Register events that enable/disable the keyboard manager while raw
777 780 // input is focused.
778 IPython.keyboard_manager.register_events(raw_input);
781 this.keyboard_manager.register_events(raw_input);
779 782 // Note, the following line used to read raw_input.focus().focus().
780 783 // This seemed to be needed otherwise only the cell would be focused.
781 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 798 var content = {
796 799 output_type : 'stream',
797 name : 'stdout',
800 stream : 'stdout',
798 801 text : theprompt.text() + echo + '\n'
799 802 }
800 803 // remove form container
801 804 container.parent().remove();
802 805 // replace with plaintext version in stdout
803 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 995 "application/pdf" : append_pdf
993 996 };
994 997
998 // For backwards compatability.
995 999 IPython.OutputArea = OutputArea;
996 1000
997 return IPython;
998
999 }(IPython));
1001 return {'OutputArea': OutputArea};
1002 });
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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
1 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
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
1 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
1 NO CONTENT: file renamed from examples/Interactive Widgets/Custom Widgets.ipynb to examples/Interactive Widgets/Date Picker Widget.ipynb
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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
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
1 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
1 NO CONTENT: file renamed from examples/Notebook/images/python_logo.svg to examples/images/python_logo.svg
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
The requested commit or file is too big and content was truncated. Show full diff
1 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
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