##// END OF EJS Templates
Merge branch 'master' into interact-range-widgets...
Gordon Ball -
r17589:157df08f 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,81 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 this.events.trigger('spec_changed.Kernel', ks);
58 this.notebook.session.delete();
59 this.notebook.start_session(kernel_name);
60 };
61
62 KernelSelector.prototype.bind_events = function() {
63 var that = this;
64 this.events.on('spec_changed.Kernel', function(event, data) {
65 that.current_selection = data.name;
66 that.element.find("#current_kernel_spec").find('.kernel_name').text(data.display_name);
67 });
68
69 this.events.on('started.Session', function(events, session) {
70 if (session.kernel_name !== that.current_selection) {
71 // If we created a 'python' session, we only know if it's Python
72 // 3 or 2 on the server's reply, so we fire the event again to
73 // set things up.
74 var ks = that.kernelspecs[session.kernel_name];
75 that.events.trigger('spec_changed.Kernel', ks);
76 }
77 });
78 };
79
80 return {'KernelSelector': KernelSelector};
81 });
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 120000
NO CONTENT: new file 120000
The requested commit or file is too big and content was truncated. Show full diff
@@ -7,6 +7,7 b' docs/source/api/generated'
7 docs/source/config/options
7 docs/source/config/options
8 docs/gh-pages
8 docs/gh-pages
9 IPython/html/notebook/static/mathjax
9 IPython/html/notebook/static/mathjax
10 IPython/html/static/style/*.map
10 *.py[co]
11 *.py[co]
11 __pycache__
12 __pycache__
12 *.egg-info
13 *.egg-info
@@ -13,8 +13,10 b' before_install:'
13 # Pierre Carrier's PPA for PhantomJS and CasperJS
13 # Pierre Carrier's PPA for PhantomJS and CasperJS
14 - time sudo add-apt-repository -y ppa:pcarrier/ppa
14 - time sudo add-apt-repository -y ppa:pcarrier/ppa
15 - time sudo apt-get update
15 - time sudo apt-get update
16 - time sudo apt-get install pandoc casperjs nodejs libzmq3-dev
16 - time sudo apt-get install pandoc casperjs libzmq3-dev
17 - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests mock pyzmq jsonschema jsonpointer
17 # pin tornado < 4 for js tests while phantom is on super old webkit
18 - if [[ $GROUP == 'js' ]]; then pip install 'tornado<4'; fi
19 - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests mock pyzmq jsonschema jsonpointer mistune
18 install:
20 install:
19 - time python setup.py install -q
21 - time python setup.py install -q
20 script:
22 script:
@@ -1,31 +1,11 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """A base class for objects that are configurable."""
3 A base class for objects that are configurable.
4
3
5 Inheritance diagram:
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 .. inheritance-diagram:: IPython.config.configurable
8 :parts: 3
9
10 Authors:
11
12 * Brian Granger
13 * Fernando Perez
14 * Min RK
15 """
16 from __future__ import print_function
7 from __future__ import print_function
17
8
18 #-----------------------------------------------------------------------------
19 # Copyright (C) 2008-2011 The IPython Development Team
20 #
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
23 #-----------------------------------------------------------------------------
24
25 #-----------------------------------------------------------------------------
26 # Imports
27 #-----------------------------------------------------------------------------
28
29 import logging
9 import logging
30 from copy import deepcopy
10 from copy import deepcopy
31
11
@@ -375,16 +355,12 b' class LoggingConfigurable(Configurable):'
375 """A parent class for Configurables that log.
355 """A parent class for Configurables that log.
376
356
377 Subclasses have a log trait, and the default behavior
357 Subclasses have a log trait, and the default behavior
378 is to get the logger from the currently running Application
358 is to get the logger from the currently running Application.
379 via Application.instance().log.
380 """
359 """
381
360
382 log = Instance('logging.Logger')
361 log = Instance('logging.Logger')
383 def _log_default(self):
362 def _log_default(self):
384 from IPython.config.application import Application
363 from IPython.utils import log
385 if Application.initialized():
364 return log.get_logger()
386 return Application.instance().log
387 else:
388 return logging.getLogger()
389
365
390
366
@@ -1,27 +1,8 b''
1 """A simple configuration system.
1 # encoding: utf-8
2 """A simple configuration system."""
2
3
3 Inheritance diagram:
4 # Copyright (c) IPython Development Team.
4
5 # Distributed under the terms of the Modified BSD License.
5 .. inheritance-diagram:: IPython.config.loader
6 :parts: 3
7
8 Authors
9 -------
10 * Brian Granger
11 * Fernando Perez
12 * Min RK
13 """
14
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
21
22 #-----------------------------------------------------------------------------
23 # Imports
24 #-----------------------------------------------------------------------------
25
6
26 import argparse
7 import argparse
27 import copy
8 import copy
@@ -308,11 +289,8 b' class ConfigLoader(object):'
308 """
289 """
309
290
310 def _log_default(self):
291 def _log_default(self):
311 from IPython.config.application import Application
292 from IPython.utils.log import get_logger
312 if Application.initialized():
293 return get_logger()
313 return Application.instance().log
314 else:
315 return logging.getLogger()
316
294
317 def __init__(self, log=None):
295 def __init__(self, log=None):
318 """A base class for config loaders.
296 """A base class for config loaders.
@@ -165,8 +165,6 b' class IPythonConsoleApp(ConnectionFileMixin):'
165 if argv is None:
165 if argv is None:
166 argv = sys.argv[1:]
166 argv = sys.argv[1:]
167 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
167 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
168 # kernel should inherit default config file from frontend
169 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
170
168
171 def init_connection_file(self):
169 def init_connection_file(self):
172 """find the connection file, and load the info if found.
170 """find the connection file, and load the info if found.
@@ -289,6 +287,7 b' class IPythonConsoleApp(ConnectionFileMixin):'
289 try:
287 try:
290 self.kernel_manager = self.kernel_manager_class(
288 self.kernel_manager = self.kernel_manager_class(
291 ip=self.ip,
289 ip=self.ip,
290 session=self.session,
292 transport=self.transport,
291 transport=self.transport,
293 shell_port=self.shell_port,
292 shell_port=self.shell_port,
294 iopub_port=self.iopub_port,
293 iopub_port=self.iopub_port,
@@ -326,6 +325,7 b' class IPythonConsoleApp(ConnectionFileMixin):'
326 self.kernel_client = self.kernel_manager.client()
325 self.kernel_client = self.kernel_manager.client()
327 else:
326 else:
328 self.kernel_client = self.kernel_client_class(
327 self.kernel_client = self.kernel_client_class(
328 session=self.session,
329 ip=self.ip,
329 ip=self.ip,
330 transport=self.transport,
330 transport=self.transport,
331 shell_port=self.shell_port,
331 shell_port=self.shell_port,
@@ -80,6 +80,7 b' from IPython.core.error import TryNext'
80 from IPython.core.inputsplitter import ESC_MAGIC
80 from IPython.core.inputsplitter import ESC_MAGIC
81 from IPython.utils import generics
81 from IPython.utils import generics
82 from IPython.utils import io
82 from IPython.utils import io
83 from IPython.utils.decorators import undoc
83 from IPython.utils.dir2 import dir2
84 from IPython.utils.dir2 import dir2
84 from IPython.utils.process import arg_split
85 from IPython.utils.process import arg_split
85 from IPython.utils.py3compat import builtin_mod, string_types
86 from IPython.utils.py3compat import builtin_mod, string_types
@@ -216,7 +217,7 b' def penalize_magics_key(word):'
216 return word
217 return word
217
218
218
219
219
220 @undoc
220 class Bunch(object): pass
221 class Bunch(object): pass
221
222
222
223
@@ -865,6 +866,7 b' class IPCompleter(Completer):'
865 return argMatches
866 return argMatches
866
867
867 def dict_key_matches(self, text):
868 def dict_key_matches(self, text):
869 "Match string keys in a dictionary, after e.g. 'foo[' "
868 def get_keys(obj):
870 def get_keys(obj):
869 # Only allow completion for known in-memory dict-like types
871 # Only allow completion for known in-memory dict-like types
870 if isinstance(obj, dict) or\
872 if isinstance(obj, dict) or\
@@ -1010,9 +1012,6 b' class IPCompleter(Completer):'
1010 def complete(self, text=None, line_buffer=None, cursor_pos=None):
1012 def complete(self, text=None, line_buffer=None, cursor_pos=None):
1011 """Find completions for the given text and line context.
1013 """Find completions for the given text and line context.
1012
1014
1013 This is called successively with state == 0, 1, 2, ... until it
1014 returns None. The completion should begin with 'text'.
1015
1016 Note that both the text and the line_buffer are optional, but at least
1015 Note that both the text and the line_buffer are optional, but at least
1017 one of them must be given.
1016 one of them must be given.
1018
1017
@@ -26,7 +26,13 b' from IPython.core.formatters import _safe_get_formatter_method'
26 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
26 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
27 unicode_type)
27 unicode_type)
28 from IPython.testing.skipdoctest import skip_doctest
28 from IPython.testing.skipdoctest import skip_doctest
29 from .displaypub import publish_display_data
29
30 __all__ = ['display', 'display_pretty', 'display_html', 'display_markdown',
31 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
32 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
33 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'JSON', 'Javascript',
34 'Image', 'clear_output', 'set_matplotlib_formats', 'set_matplotlib_close',
35 'publish_display_data']
30
36
31 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
32 # utility functions
38 # utility functions
@@ -78,6 +84,48 b' def _display_mimetype(mimetype, objs, raw=False, metadata=None):'
78 # Main functions
84 # Main functions
79 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
80
86
87 def publish_display_data(data, metadata=None, source=None):
88 """Publish data and metadata to all frontends.
89
90 See the ``display_data`` message in the messaging documentation for
91 more details about this message type.
92
93 The following MIME types are currently implemented:
94
95 * text/plain
96 * text/html
97 * text/markdown
98 * text/latex
99 * application/json
100 * application/javascript
101 * image/png
102 * image/jpeg
103 * image/svg+xml
104
105 Parameters
106 ----------
107 data : dict
108 A dictionary having keys that are valid MIME types (like
109 'text/plain' or 'image/svg+xml') and values that are the data for
110 that MIME type. The data itself must be a JSON'able data
111 structure. Minimally all data should have the 'text/plain' data,
112 which can be displayed by all frontends. If more than the plain
113 text is given, it is up to the frontend to decide which
114 representation to use.
115 metadata : dict
116 A dictionary for metadata related to the data. This can contain
117 arbitrary key, value pairs that frontends can use to interpret
118 the data. mime-type keys matching those in data can be used
119 to specify metadata about particular representations.
120 source : str, deprecated
121 Unused.
122 """
123 from IPython.core.interactiveshell import InteractiveShell
124 InteractiveShell.instance().display_pub.publish(
125 data=data,
126 metadata=metadata,
127 )
128
81 def display(*objs, **kwargs):
129 def display(*objs, **kwargs):
82 """Display a Python object in all frontends.
130 """Display a Python object in all frontends.
83
131
@@ -19,9 +19,11 b' from __future__ import print_function'
19
19
20 from IPython.config.configurable import Configurable
20 from IPython.config.configurable import Configurable
21 from IPython.utils import io
21 from IPython.utils import io
22 from IPython.utils.py3compat import string_types
23 from IPython.utils.traitlets import List
22 from IPython.utils.traitlets import List
24
23
24 # This used to be defined here - it is imported for backwards compatibility
25 from .display import publish_display_data
26
25 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
26 # Main payload class
28 # Main payload class
27 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
@@ -112,48 +114,3 b' class CapturingDisplayPublisher(DisplayPublisher):'
112
114
113 # empty the list, *do not* reassign a new list
115 # empty the list, *do not* reassign a new list
114 del self.outputs[:]
116 del self.outputs[:]
115
116
117 def publish_display_data(data, metadata=None, source=None):
118 """Publish data and metadata to all frontends.
119
120 See the ``display_data`` message in the messaging documentation for
121 more details about this message type.
122
123 The following MIME types are currently implemented:
124
125 * text/plain
126 * text/html
127 * text/markdown
128 * text/latex
129 * application/json
130 * application/javascript
131 * image/png
132 * image/jpeg
133 * image/svg+xml
134
135 Parameters
136 ----------
137 data : dict
138 A dictionary having keys that are valid MIME types (like
139 'text/plain' or 'image/svg+xml') and values that are the data for
140 that MIME type. The data itself must be a JSON'able data
141 structure. Minimally all data should have the 'text/plain' data,
142 which can be displayed by all frontends. If more than the plain
143 text is given, it is up to the frontend to decide which
144 representation to use.
145 metadata : dict
146 A dictionary for metadata related to the data. This can contain
147 arbitrary key, value pairs that frontends can use to interpret
148 the data. mime-type keys matching those in data can be used
149 to specify metadata about particular representations.
150 source : str, deprecated
151 Unused.
152 """
153 from IPython.core.interactiveshell import InteractiveShell
154 InteractiveShell.instance().display_pub.publish(
155 data=data,
156 metadata=metadata,
157 )
158
159
@@ -736,12 +736,13 b' class InteractiveShell(SingletonConfigurable):'
736 # stdlib venv may symlink sys.executable, so we can't use realpath.
736 # stdlib venv may symlink sys.executable, so we can't use realpath.
737 # but others can symlink *to* the venv Python, so we can't just use sys.executable.
737 # but others can symlink *to* the venv Python, so we can't just use sys.executable.
738 # So we just check every item in the symlink tree (generally <= 3)
738 # So we just check every item in the symlink tree (generally <= 3)
739 p = sys.executable
739 p = os.path.normcase(sys.executable)
740 paths = [p]
740 paths = [p]
741 while os.path.islink(p):
741 while os.path.islink(p):
742 p = os.path.join(os.path.dirname(p), os.readlink(p))
742 p = os.path.normcase(os.path.join(os.path.dirname(p), os.readlink(p)))
743 paths.append(p)
743 paths.append(p)
744 if any(p.startswith(os.environ['VIRTUAL_ENV']) for p in paths):
744 p_venv = os.path.normcase(os.environ['VIRTUAL_ENV'])
745 if any(p.startswith(p_venv) for p in paths):
745 # Running properly in the virtualenv, don't need to do anything
746 # Running properly in the virtualenv, don't need to do anything
746 return
747 return
747
748
@@ -1735,7 +1736,7 b' class InteractiveShell(SingletonConfigurable):'
1735 This hook should be used sparingly, only in places which are not likely
1736 This hook should be used sparingly, only in places which are not likely
1736 to be true IPython errors.
1737 to be true IPython errors.
1737 """
1738 """
1738 self.showtraceback((etype,value,tb),tb_offset=0)
1739 self.showtraceback((etype, value, tb), tb_offset=0)
1739
1740
1740 def _get_exc_info(self, exc_tuple=None):
1741 def _get_exc_info(self, exc_tuple=None):
1741 """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc.
1742 """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc.
@@ -1776,7 +1777,7 b' class InteractiveShell(SingletonConfigurable):'
1776 """
1777 """
1777 self.write_err("UsageError: %s" % exc)
1778 self.write_err("UsageError: %s" % exc)
1778
1779
1779 def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None,
1780 def showtraceback(self, exc_tuple=None, filename=None, tb_offset=None,
1780 exception_only=False):
1781 exception_only=False):
1781 """Display the exception that just occurred.
1782 """Display the exception that just occurred.
1782
1783
@@ -2918,10 +2919,9 b' class InteractiveShell(SingletonConfigurable):'
2918 False : successful execution.
2919 False : successful execution.
2919 True : an error occurred.
2920 True : an error occurred.
2920 """
2921 """
2921
2922 # Set our own excepthook in case the user code tries to call it
2922 # Set our own excepthook in case the user code tries to call it
2923 # directly, so that the IPython crash handler doesn't get triggered
2923 # directly, so that the IPython crash handler doesn't get triggered
2924 old_excepthook,sys.excepthook = sys.excepthook, self.excepthook
2924 old_excepthook, sys.excepthook = sys.excepthook, self.excepthook
2925
2925
2926 # we save the original sys.excepthook in the instance, in case config
2926 # we save the original sys.excepthook in the instance, in case config
2927 # code (such as magics) needs access to it.
2927 # code (such as magics) needs access to it.
@@ -2939,8 +2939,8 b' class InteractiveShell(SingletonConfigurable):'
2939 self.showtraceback(exception_only=True)
2939 self.showtraceback(exception_only=True)
2940 warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1)
2940 warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1)
2941 except self.custom_exceptions:
2941 except self.custom_exceptions:
2942 etype,value,tb = sys.exc_info()
2942 etype, value, tb = sys.exc_info()
2943 self.CustomTB(etype,value,tb)
2943 self.CustomTB(etype, value, tb)
2944 except:
2944 except:
2945 self.showtraceback()
2945 self.showtraceback()
2946 else:
2946 else:
@@ -3087,6 +3087,7 b' class InteractiveShell(SingletonConfigurable):'
3087 self.tempdirs.append(dirname)
3087 self.tempdirs.append(dirname)
3088
3088
3089 handle, filename = tempfile.mkstemp('.py', prefix, dir=dirname)
3089 handle, filename = tempfile.mkstemp('.py', prefix, dir=dirname)
3090 os.close(handle) # On Windows, there can only be one open handle on a file
3090 self.tempfiles.append(filename)
3091 self.tempfiles.append(filename)
3091
3092
3092 if data:
3093 if data:
@@ -193,6 +193,8 b' class ScriptMagics(Magics):'
193 else:
193 else:
194 raise
194 raise
195
195
196 if not cell.endswith('\n'):
197 cell += '\n'
196 cell = cell.encode('utf8', 'replace')
198 cell = cell.encode('utf8', 'replace')
197 if args.bg:
199 if args.bg:
198 self.bg_processes.append(p)
200 self.bg_processes.append(p)
@@ -6,6 +6,7 b''
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # stdlib
8 # stdlib
9 import io
9 import os
10 import os
10 import sys
11 import sys
11 import tempfile
12 import tempfile
@@ -124,7 +125,7 b' def test_history():'
124 # Cross testing: check that magic %save can get previous session.
125 # Cross testing: check that magic %save can get previous session.
125 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
126 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
126 ip.magic("save " + testfilename + " ~1/1-3")
127 ip.magic("save " + testfilename + " ~1/1-3")
127 with py3compat.open(testfilename, encoding='utf-8') as testfile:
128 with io.open(testfilename, encoding='utf-8') as testfile:
128 nt.assert_equal(testfile.read(),
129 nt.assert_equal(testfile.read(),
129 u"# coding: utf-8\n" + u"\n".join(hist)+u"\n")
130 u"# coding: utf-8\n" + u"\n".join(hist)+u"\n")
130
131
@@ -462,6 +462,15 b' class InteractiveShellTestCase(unittest.TestCase):'
462 ip.run_cell("d = 1/2", shell_futures=True)
462 ip.run_cell("d = 1/2", shell_futures=True)
463 self.assertEqual(ip.user_ns['d'], 0)
463 self.assertEqual(ip.user_ns['d'], 0)
464
464
465 def test_mktempfile(self):
466 filename = ip.mktempfile()
467 # Check that we can open the file again on Windows
468 with open(filename, 'w') as f:
469 f.write('abc')
470
471 filename = ip.mktempfile(data='blah')
472 with open(filename, 'r') as f:
473 self.assertEqual(f.read(), 'blah')
465
474
466 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
475 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
467
476
@@ -9,6 +9,7 b' from IPython.testing import tools as tt'
9 from IPython.testing.decorators import onlyif_unicode_paths
9 from IPython.testing.decorators import onlyif_unicode_paths
10 from IPython.utils.syspathcontext import prepended_to_syspath
10 from IPython.utils.syspathcontext import prepended_to_syspath
11 from IPython.utils.tempdir import TemporaryDirectory
11 from IPython.utils.tempdir import TemporaryDirectory
12 from IPython.utils.py3compat import PY3
12
13
13 ip = get_ipython()
14 ip = get_ipython()
14
15
@@ -147,3 +148,37 b' class SyntaxErrorTest(unittest.TestCase):'
147 except ValueError:
148 except ValueError:
148 with tt.AssertPrints('QWERTY'):
149 with tt.AssertPrints('QWERTY'):
149 ip.showsyntaxerror()
150 ip.showsyntaxerror()
151
152
153 class Python3ChainedExceptionsTest(unittest.TestCase):
154 DIRECT_CAUSE_ERROR_CODE = """
155 try:
156 x = 1 + 2
157 print(not_defined_here)
158 except Exception as e:
159 x += 55
160 x - 1
161 y = {}
162 raise KeyError('uh') from e
163 """
164
165 EXCEPTION_DURING_HANDLING_CODE = """
166 try:
167 x = 1 + 2
168 print(not_defined_here)
169 except Exception as e:
170 x += 55
171 x - 1
172 y = {}
173 raise KeyError('uh')
174 """
175
176 def test_direct_cause_error(self):
177 if PY3:
178 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
179 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
180
181 def test_exception_during_handling_error(self):
182 if PY3:
183 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
184 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
This diff has been collapsed as it changes many lines, (529 lines changed) Show them Hide them
@@ -39,7 +39,7 b" Give it a shot--you'll love it or you'll hate it."
39 Verbose).
39 Verbose).
40
40
41
41
42 Installation instructions for ColorTB::
42 Installation instructions for VerboseTB::
43
43
44 import sys,ultratb
44 import sys,ultratb
45 sys.excepthook = ultratb.VerboseTB()
45 sys.excepthook = ultratb.VerboseTB()
@@ -73,11 +73,11 b' Inheritance diagram:'
73 """
73 """
74
74
75 #*****************************************************************************
75 #*****************************************************************************
76 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
76 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
77 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
77 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
78 #
78 #
79 # Distributed under the terms of the BSD License. The full license is in
79 # Distributed under the terms of the BSD License. The full license is in
80 # the file COPYING, distributed as part of this software.
80 # the file COPYING, distributed as part of this software.
81 #*****************************************************************************
81 #*****************************************************************************
82
82
83 from __future__ import unicode_literals
83 from __future__ import unicode_literals
@@ -95,14 +95,14 b' import tokenize'
95 import traceback
95 import traceback
96 import types
96 import types
97
97
98 try: # Python 2
98 try: # Python 2
99 generate_tokens = tokenize.generate_tokens
99 generate_tokens = tokenize.generate_tokens
100 except AttributeError: # Python 3
100 except AttributeError: # Python 3
101 generate_tokens = tokenize.tokenize
101 generate_tokens = tokenize.tokenize
102
102
103 # For purposes of monkeypatching inspect to fix a bug in it.
103 # For purposes of monkeypatching inspect to fix a bug in it.
104 from inspect import getsourcefile, getfile, getmodule,\
104 from inspect import getsourcefile, getfile, getmodule, \
105 ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode
105 ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode
106
106
107 # IPython's own modules
107 # IPython's own modules
108 # Modified pdb which doesn't damage IPython's readline handling
108 # Modified pdb which doesn't damage IPython's readline handling
@@ -125,11 +125,11 b' INDENT_SIZE = 8'
125
125
126 # Default color scheme. This is used, for example, by the traceback
126 # Default color scheme. This is used, for example, by the traceback
127 # formatter. When running in an actual IPython instance, the user's rc.colors
127 # formatter. When running in an actual IPython instance, the user's rc.colors
128 # value is used, but havinga module global makes this functionality available
128 # value is used, but having a module global makes this functionality available
129 # to users of ultratb who are NOT running inside ipython.
129 # to users of ultratb who are NOT running inside ipython.
130 DEFAULT_SCHEME = 'NoColor'
130 DEFAULT_SCHEME = 'NoColor'
131
131
132 #---------------------------------------------------------------------------
132 # ---------------------------------------------------------------------------
133 # Code begins
133 # Code begins
134
134
135 # Utility functions
135 # Utility functions
@@ -141,6 +141,7 b' def inspect_error():'
141 error('Internal Python error in the inspect module.\n'
141 error('Internal Python error in the inspect module.\n'
142 'Below is the traceback from this internal error.\n')
142 'Below is the traceback from this internal error.\n')
143
143
144
144 # This function is a monkeypatch we apply to the Python inspect module. We have
145 # This function is a monkeypatch we apply to the Python inspect module. We have
145 # now found when it's needed (see discussion on issue gh-1456), and we have a
146 # now found when it's needed (see discussion on issue gh-1456), and we have a
146 # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if
147 # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if
@@ -212,7 +213,7 b' def findsource(object):'
212 pmatch = pat.match
213 pmatch = pat.match
213 # fperez - fix: sometimes, co_firstlineno can give a number larger than
214 # fperez - fix: sometimes, co_firstlineno can give a number larger than
214 # the length of lines, which causes an error. Safeguard against that.
215 # the length of lines, which causes an error. Safeguard against that.
215 lnum = min(object.co_firstlineno,len(lines))-1
216 lnum = min(object.co_firstlineno, len(lines)) - 1
216 while lnum > 0:
217 while lnum > 0:
217 if pmatch(lines[lnum]): break
218 if pmatch(lines[lnum]): break
218 lnum -= 1
219 lnum -= 1
@@ -220,9 +221,11 b' def findsource(object):'
220 return lines, lnum
221 return lines, lnum
221 raise IOError('could not find code object')
222 raise IOError('could not find code object')
222
223
224
223 # Monkeypatch inspect to apply our bugfix.
225 # Monkeypatch inspect to apply our bugfix.
224 def with_patch_inspect(f):
226 def with_patch_inspect(f):
225 """decorator for monkeypatching inspect.findsource"""
227 """decorator for monkeypatching inspect.findsource"""
228
226 def wrapped(*args, **kwargs):
229 def wrapped(*args, **kwargs):
227 save_findsource = inspect.findsource
230 save_findsource = inspect.findsource
228 inspect.findsource = findsource
231 inspect.findsource = findsource
@@ -230,8 +233,10 b' def with_patch_inspect(f):'
230 return f(*args, **kwargs)
233 return f(*args, **kwargs)
231 finally:
234 finally:
232 inspect.findsource = save_findsource
235 inspect.findsource = save_findsource
236
233 return wrapped
237 return wrapped
234
238
239
235 def fix_frame_records_filenames(records):
240 def fix_frame_records_filenames(records):
236 """Try to fix the filenames in each record from inspect.getinnerframes().
241 """Try to fix the filenames in each record from inspect.getinnerframes().
237
242
@@ -253,11 +258,10 b' def fix_frame_records_filenames(records):'
253
258
254
259
255 @with_patch_inspect
260 @with_patch_inspect
256 def _fixed_getinnerframes(etb, context=1,tb_offset=0):
261 def _fixed_getinnerframes(etb, context=1, tb_offset=0):
257 LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5
262 LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5
258
259 records = fix_frame_records_filenames(inspect.getinnerframes(etb, context))
260
263
264 records = fix_frame_records_filenames(inspect.getinnerframes(etb, context))
261 # If the error is at the console, don't build any context, since it would
265 # If the error is at the console, don't build any context, since it would
262 # otherwise produce 5 blank lines printed out (there is no file at the
266 # otherwise produce 5 blank lines printed out (there is no file at the
263 # console)
267 # console)
@@ -272,9 +276,9 b' def _fixed_getinnerframes(etb, context=1,tb_offset=0):'
272 aux = traceback.extract_tb(etb)
276 aux = traceback.extract_tb(etb)
273 assert len(records) == len(aux)
277 assert len(records) == len(aux)
274 for i, (file, lnum, _, _) in zip(range(len(records)), aux):
278 for i, (file, lnum, _, _) in zip(range(len(records)), aux):
275 maybeStart = lnum-1 - context//2
279 maybeStart = lnum - 1 - context // 2
276 start = max(maybeStart, 0)
280 start = max(maybeStart, 0)
277 end = start + context
281 end = start + context
278 lines = ulinecache.getlines(file)[start:end]
282 lines = ulinecache.getlines(file)[start:end]
279 buf = list(records[i])
283 buf = list(records[i])
280 buf[LNUM_POS] = lnum
284 buf[LNUM_POS] = lnum
@@ -290,7 +294,8 b' def _fixed_getinnerframes(etb, context=1,tb_offset=0):'
290
294
291 _parser = PyColorize.Parser()
295 _parser = PyColorize.Parser()
292
296
293 def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):
297
298 def _format_traceback_lines(lnum, index, lines, Colors, lvals=None, scheme=None):
294 numbers_width = INDENT_SIZE - 1
299 numbers_width = INDENT_SIZE - 1
295 res = []
300 res = []
296 i = lnum - index
301 i = lnum - index
@@ -315,7 +320,7 b' def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):'
315 # This is the line with the error
320 # This is the line with the error
316 pad = numbers_width - len(str(i))
321 pad = numbers_width - len(str(i))
317 if pad >= 3:
322 if pad >= 3:
318 marker = '-'*(pad-3) + '-> '
323 marker = '-' * (pad - 3) + '-> '
319 elif pad == 2:
324 elif pad == 2:
320 marker = '> '
325 marker = '> '
321 elif pad == 1:
326 elif pad == 1:
@@ -323,12 +328,12 b' def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):'
323 else:
328 else:
324 marker = ''
329 marker = ''
325 num = marker + str(i)
330 num = marker + str(i)
326 line = '%s%s%s %s%s' %(Colors.linenoEm, num,
331 line = '%s%s%s %s%s' % (Colors.linenoEm, num,
327 Colors.line, line, Colors.Normal)
332 Colors.line, line, Colors.Normal)
328 else:
333 else:
329 num = '%*s' % (numbers_width,i)
334 num = '%*s' % (numbers_width, i)
330 line = '%s%s%s %s' %(Colors.lineno, num,
335 line = '%s%s%s %s' % (Colors.lineno, num,
331 Colors.Normal, line)
336 Colors.Normal, line)
332
337
333 res.append(line)
338 res.append(line)
334 if lvals and i == lnum:
339 if lvals and i == lnum:
@@ -389,16 +394,16 b' class TBTools(object):'
389
394
390 ostream = property(_get_ostream, _set_ostream)
395 ostream = property(_get_ostream, _set_ostream)
391
396
392 def set_colors(self,*args,**kw):
397 def set_colors(self, *args, **kw):
393 """Shorthand access to the color table scheme selector method."""
398 """Shorthand access to the color table scheme selector method."""
394
399
395 # Set own color table
400 # Set own color table
396 self.color_scheme_table.set_active_scheme(*args,**kw)
401 self.color_scheme_table.set_active_scheme(*args, **kw)
397 # for convenience, set Colors to the active scheme
402 # for convenience, set Colors to the active scheme
398 self.Colors = self.color_scheme_table.active_colors
403 self.Colors = self.color_scheme_table.active_colors
399 # Also set colors of debugger
404 # Also set colors of debugger
400 if hasattr(self,'pdb') and self.pdb is not None:
405 if hasattr(self, 'pdb') and self.pdb is not None:
401 self.pdb.set_colors(*args,**kw)
406 self.pdb.set_colors(*args, **kw)
402
407
403 def color_toggle(self):
408 def color_toggle(self):
404 """Toggle between the currently active color scheme and NoColor."""
409 """Toggle between the currently active color scheme and NoColor."""
@@ -453,7 +458,7 b' class ListTB(TBTools):'
453 Because they are meant to be called without a full traceback (only a
458 Because they are meant to be called without a full traceback (only a
454 list), instances of this class can't call the interactive pdb debugger."""
459 list), instances of this class can't call the interactive pdb debugger."""
455
460
456 def __init__(self,color_scheme = 'NoColor', call_pdb=False, ostream=None):
461 def __init__(self, color_scheme='NoColor', call_pdb=False, ostream=None):
457 TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
462 TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
458 ostream=ostream)
463 ostream=ostream)
459
464
@@ -497,7 +502,7 b' class ListTB(TBTools):'
497 elist = elist[tb_offset:]
502 elist = elist[tb_offset:]
498
503
499 out_list.append('Traceback %s(most recent call last)%s:' %
504 out_list.append('Traceback %s(most recent call last)%s:' %
500 (Colors.normalEm, Colors.Normal) + '\n')
505 (Colors.normalEm, Colors.Normal) + '\n')
501 out_list.extend(self._format_list(elist))
506 out_list.extend(self._format_list(elist))
502 # The exception info should be a single entry in the list.
507 # The exception info should be a single entry in the list.
503 lines = ''.join(self._format_exception_only(etype, value))
508 lines = ''.join(self._format_exception_only(etype, value))
@@ -510,7 +515,7 b' class ListTB(TBTools):'
510 ## out_list.append(lines[-1])
515 ## out_list.append(lines[-1])
511
516
512 # This means it was indenting everything but the last line by a little
517 # This means it was indenting everything but the last line by a little
513 # bit. I've disabled this for now, but if we see ugliness somewhre we
518 # bit. I've disabled this for now, but if we see ugliness somewhere we
514 # can restore it.
519 # can restore it.
515
520
516 return out_list
521 return out_list
@@ -532,25 +537,24 b' class ListTB(TBTools):'
532 list = []
537 list = []
533 for filename, lineno, name, line in extracted_list[:-1]:
538 for filename, lineno, name, line in extracted_list[:-1]:
534 item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \
539 item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \
535 (Colors.filename, filename, Colors.Normal,
540 (Colors.filename, filename, Colors.Normal,
536 Colors.lineno, lineno, Colors.Normal,
541 Colors.lineno, lineno, Colors.Normal,
537 Colors.name, name, Colors.Normal)
542 Colors.name, name, Colors.Normal)
538 if line:
543 if line:
539 item += ' %s\n' % line.strip()
544 item += ' %s\n' % line.strip()
540 list.append(item)
545 list.append(item)
541 # Emphasize the last entry
546 # Emphasize the last entry
542 filename, lineno, name, line = extracted_list[-1]
547 filename, lineno, name, line = extracted_list[-1]
543 item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \
548 item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \
544 (Colors.normalEm,
549 (Colors.normalEm,
545 Colors.filenameEm, filename, Colors.normalEm,
550 Colors.filenameEm, filename, Colors.normalEm,
546 Colors.linenoEm, lineno, Colors.normalEm,
551 Colors.linenoEm, lineno, Colors.normalEm,
547 Colors.nameEm, name, Colors.normalEm,
552 Colors.nameEm, name, Colors.normalEm,
548 Colors.Normal)
553 Colors.Normal)
549 if line:
554 if line:
550 item += '%s %s%s\n' % (Colors.line, line.strip(),
555 item += '%s %s%s\n' % (Colors.line, line.strip(),
551 Colors.Normal)
556 Colors.Normal)
552 list.append(item)
557 list.append(item)
553 #from pprint import pformat; print 'LISTTB', pformat(list) # dbg
554 return list
558 return list
555
559
556 def _format_exception_only(self, etype, value):
560 def _format_exception_only(self, etype, value):
@@ -572,11 +576,10 b' class ListTB(TBTools):'
572 stype = Colors.excName + etype.__name__ + Colors.Normal
576 stype = Colors.excName + etype.__name__ + Colors.Normal
573 if value is None:
577 if value is None:
574 # Not sure if this can still happen in Python 2.6 and above
578 # Not sure if this can still happen in Python 2.6 and above
575 list.append( py3compat.cast_unicode(stype) + '\n')
579 list.append(py3compat.cast_unicode(stype) + '\n')
576 else:
580 else:
577 if issubclass(etype, SyntaxError):
581 if issubclass(etype, SyntaxError):
578 have_filedata = True
582 have_filedata = True
579 #print 'filename is',filename # dbg
580 if not value.filename: value.filename = "<string>"
583 if not value.filename: value.filename = "<string>"
581 if value.lineno:
584 if value.lineno:
582 lineno = value.lineno
585 lineno = value.lineno
@@ -585,9 +588,9 b' class ListTB(TBTools):'
585 lineno = 'unknown'
588 lineno = 'unknown'
586 textline = ''
589 textline = ''
587 list.append('%s File %s"%s"%s, line %s%s%s\n' % \
590 list.append('%s File %s"%s"%s, line %s%s%s\n' % \
588 (Colors.normalEm,
591 (Colors.normalEm,
589 Colors.filenameEm, py3compat.cast_unicode(value.filename), Colors.normalEm,
592 Colors.filenameEm, py3compat.cast_unicode(value.filename), Colors.normalEm,
590 Colors.linenoEm, lineno, Colors.Normal ))
593 Colors.linenoEm, lineno, Colors.Normal ))
591 if textline == '':
594 if textline == '':
592 textline = py3compat.cast_unicode(value.text, "utf-8")
595 textline = py3compat.cast_unicode(value.text, "utf-8")
593
596
@@ -600,13 +603,13 b' class ListTB(TBTools):'
600 Colors.Normal))
603 Colors.Normal))
601 if value.offset is not None:
604 if value.offset is not None:
602 s = ' '
605 s = ' '
603 for c in textline[i:value.offset-1]:
606 for c in textline[i:value.offset - 1]:
604 if c.isspace():
607 if c.isspace():
605 s += c
608 s += c
606 else:
609 else:
607 s += ' '
610 s += ' '
608 list.append('%s%s^%s\n' % (Colors.caret, s,
611 list.append('%s%s^%s\n' % (Colors.caret, s,
609 Colors.Normal) )
612 Colors.Normal))
610
613
611 try:
614 try:
612 s = value.msg
615 s = value.msg
@@ -636,7 +639,6 b' class ListTB(TBTools):'
636 """
639 """
637 return ListTB.structured_traceback(self, etype, value, [])
640 return ListTB.structured_traceback(self, etype, value, [])
638
641
639
640 def show_exception_only(self, etype, evalue):
642 def show_exception_only(self, etype, evalue):
641 """Only print the exception type and message, without a traceback.
643 """Only print the exception type and message, without a traceback.
642
644
@@ -659,6 +661,7 b' class ListTB(TBTools):'
659 except:
661 except:
660 return '<unprintable %s object>' % type(value).__name__
662 return '<unprintable %s object>' % type(value).__name__
661
663
664
662 #----------------------------------------------------------------------------
665 #----------------------------------------------------------------------------
663 class VerboseTB(TBTools):
666 class VerboseTB(TBTools):
664 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
667 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
@@ -668,7 +671,7 b' class VerboseTB(TBTools):'
668 traceback, to be used with alternate interpreters (because their own code
671 traceback, to be used with alternate interpreters (because their own code
669 would appear in the traceback)."""
672 would appear in the traceback)."""
670
673
671 def __init__(self,color_scheme = 'Linux', call_pdb=False, ostream=None,
674 def __init__(self, color_scheme='Linux', call_pdb=False, ostream=None,
672 tb_offset=0, long_header=False, include_vars=True,
675 tb_offset=0, long_header=False, include_vars=True,
673 check_cache=None):
676 check_cache=None):
674 """Specify traceback offset, headers and color scheme.
677 """Specify traceback offset, headers and color scheme.
@@ -691,126 +694,37 b' class VerboseTB(TBTools):'
691 check_cache = linecache.checkcache
694 check_cache = linecache.checkcache
692 self.check_cache = check_cache
695 self.check_cache = check_cache
693
696
694 def structured_traceback(self, etype, evalue, etb, tb_offset=None,
697 def format_records(self, records):
695 context=5):
698 Colors = self.Colors # just a shorthand + quicker name lookup
696 """Return a nice text document describing the traceback."""
699 ColorsNormal = Colors.Normal # used a lot
697
700 col_scheme = self.color_scheme_table.active_scheme_name
698 tb_offset = self.tb_offset if tb_offset is None else tb_offset
701 indent = ' ' * INDENT_SIZE
699
702 em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal)
700 # some locals
703 undefined = '%sundefined%s' % (Colors.em, ColorsNormal)
701 try:
702 etype = etype.__name__
703 except AttributeError:
704 pass
705 Colors = self.Colors # just a shorthand + quicker name lookup
706 ColorsNormal = Colors.Normal # used a lot
707 col_scheme = self.color_scheme_table.active_scheme_name
708 indent = ' '*INDENT_SIZE
709 em_normal = '%s\n%s%s' % (Colors.valEm, indent,ColorsNormal)
710 undefined = '%sundefined%s' % (Colors.em, ColorsNormal)
711 exc = '%s%s%s' % (Colors.excName,etype,ColorsNormal)
712
713 # some internal-use functions
714 def text_repr(value):
715 """Hopefully pretty robust repr equivalent."""
716 # this is pretty horrible but should always return *something*
717 try:
718 return pydoc.text.repr(value)
719 except KeyboardInterrupt:
720 raise
721 except:
722 try:
723 return repr(value)
724 except KeyboardInterrupt:
725 raise
726 except:
727 try:
728 # all still in an except block so we catch
729 # getattr raising
730 name = getattr(value, '__name__', None)
731 if name:
732 # ick, recursion
733 return text_repr(name)
734 klass = getattr(value, '__class__', None)
735 if klass:
736 return '%s instance' % text_repr(klass)
737 except KeyboardInterrupt:
738 raise
739 except:
740 return 'UNRECOVERABLE REPR FAILURE'
741 def eqrepr(value, repr=text_repr): return '=%s' % repr(value)
742 def nullrepr(value, repr=text_repr): return ''
743
744 # meat of the code begins
745 try:
746 etype = etype.__name__
747 except AttributeError:
748 pass
749
750 if self.long_header:
751 # Header with the exception type, python version, and date
752 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
753 date = time.ctime(time.time())
754
755 head = '%s%s%s\n%s%s%s\n%s' % (Colors.topline, '-'*75, ColorsNormal,
756 exc, ' '*(75-len(str(etype))-len(pyver)),
757 pyver, date.rjust(75) )
758 head += "\nA problem occured executing Python code. Here is the sequence of function"\
759 "\ncalls leading up to the error, with the most recent (innermost) call last."
760 else:
761 # Simplified header
762 head = '%s%s%s\n%s%s' % (Colors.topline, '-'*75, ColorsNormal,exc,
763 'Traceback (most recent call last)'.\
764 rjust(75 - len(str(etype)) ) )
765 frames = []
704 frames = []
766 # Flush cache before calling inspect. This helps alleviate some of the
767 # problems with python 2.3's inspect.py.
768 ##self.check_cache()
769 # Drop topmost frames if requested
770 try:
771 # Try the default getinnerframes and Alex's: Alex's fixes some
772 # problems, but it generates empty tracebacks for console errors
773 # (5 blanks lines) where none should be returned.
774 #records = inspect.getinnerframes(etb, context)[tb_offset:]
775 #print 'python records:', records # dbg
776 records = _fixed_getinnerframes(etb, context, tb_offset)
777 #print 'alex records:', records # dbg
778 except:
779
780 # FIXME: I've been getting many crash reports from python 2.3
781 # users, traceable to inspect.py. If I can find a small test-case
782 # to reproduce this, I should either write a better workaround or
783 # file a bug report against inspect (if that's the real problem).
784 # So far, I haven't been able to find an isolated example to
785 # reproduce the problem.
786 inspect_error()
787 traceback.print_exc(file=self.ostream)
788 info('\nUnfortunately, your original traceback can not be constructed.\n')
789 return ''
790
791 # build some color string templates outside these nested loops
705 # build some color string templates outside these nested loops
792 tpl_link = '%s%%s%s' % (Colors.filenameEm,ColorsNormal)
706 tpl_link = '%s%%s%s' % (Colors.filenameEm, ColorsNormal)
793 tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm,
707 tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm,
794 ColorsNormal)
708 ColorsNormal)
795 tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \
709 tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \
796 (Colors.vName, Colors.valEm, ColorsNormal)
710 (Colors.vName, Colors.valEm, ColorsNormal)
797 tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal)
711 tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal)
798 tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal,
712 tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal,
799 Colors.vName, ColorsNormal)
713 Colors.vName, ColorsNormal)
800 tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal)
714 tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal)
801 tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal)
715
802 tpl_line_em = '%s%%s%s %%s%s' % (Colors.linenoEm,Colors.line,
716 tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal)
803 ColorsNormal)
717 tpl_line_em = '%s%%s%s %%s%s' % (Colors.linenoEm, Colors.line,
718 ColorsNormal)
804
719
805 # now, loop over all records printing context and info
806 abspath = os.path.abspath
720 abspath = os.path.abspath
807 for frame, file, lnum, func, lines, index in records:
721 for frame, file, lnum, func, lines, index in records:
808 #print '*** record:',file,lnum,func,lines,index # dbg
722 #print '*** record:',file,lnum,func,lines,index # dbg
809 if not file:
723 if not file:
810 file = '?'
724 file = '?'
811 elif not(file.startswith(str("<")) and file.endswith(str(">"))):
725 elif not (file.startswith(str("<")) and file.endswith(str(">"))):
812 # Guess that filenames like <string> aren't real filenames, so
726 # Guess that filenames like <string> aren't real filenames, so
813 # don't call abspath on them.
727 # don't call abspath on them.
814 try:
728 try:
815 file = abspath(file)
729 file = abspath(file)
816 except OSError:
730 except OSError:
@@ -827,9 +741,9 b' class VerboseTB(TBTools):'
827 # Decide whether to include variable details or not
741 # Decide whether to include variable details or not
828 var_repr = self.include_vars and eqrepr or nullrepr
742 var_repr = self.include_vars and eqrepr or nullrepr
829 try:
743 try:
830 call = tpl_call % (func,inspect.formatargvalues(args,
744 call = tpl_call % (func, inspect.formatargvalues(args,
831 varargs, varkw,
745 varargs, varkw,
832 locals,formatvalue=var_repr))
746 locals, formatvalue=var_repr))
833 except KeyError:
747 except KeyError:
834 # This happens in situations like errors inside generator
748 # This happens in situations like errors inside generator
835 # expressions, where local variables are listed in the
749 # expressions, where local variables are listed in the
@@ -848,12 +762,12 b' class VerboseTB(TBTools):'
848 # will illustrate the error, if this exception catch is
762 # will illustrate the error, if this exception catch is
849 # disabled.
763 # disabled.
850 call = tpl_call_fail % func
764 call = tpl_call_fail % func
851
765
852 # Don't attempt to tokenize binary files.
766 # Don't attempt to tokenize binary files.
853 if file.endswith(('.so', '.pyd', '.dll')):
767 if file.endswith(('.so', '.pyd', '.dll')):
854 frames.append('%s %s\n' % (link,call))
768 frames.append('%s %s\n' % (link, call))
855 continue
769 continue
856 elif file.endswith(('.pyc','.pyo')):
770 elif file.endswith(('.pyc', '.pyo')):
857 # Look up the corresponding source file.
771 # Look up the corresponding source file.
858 file = openpy.source_from_cache(file)
772 file = openpy.source_from_cache(file)
859
773
@@ -867,7 +781,7 b' class VerboseTB(TBTools):'
867 try:
781 try:
868 names = []
782 names = []
869 name_cont = False
783 name_cont = False
870
784
871 for token_type, token, start, end, line in generate_tokens(linereader):
785 for token_type, token, start, end, line in generate_tokens(linereader):
872 # build composite names
786 # build composite names
873 if token_type == tokenize.NAME and token not in keyword.kwlist:
787 if token_type == tokenize.NAME and token not in keyword.kwlist:
@@ -890,7 +804,7 b' class VerboseTB(TBTools):'
890 name_cont = True
804 name_cont = True
891 elif token_type == tokenize.NEWLINE:
805 elif token_type == tokenize.NEWLINE:
892 break
806 break
893
807
894 except (IndexError, UnicodeDecodeError):
808 except (IndexError, UnicodeDecodeError):
895 # signals exit of tokenizer
809 # signals exit of tokenizer
896 pass
810 pass
@@ -909,11 +823,11 b' class VerboseTB(TBTools):'
909 lvals = []
823 lvals = []
910 if self.include_vars:
824 if self.include_vars:
911 for name_full in unique_names:
825 for name_full in unique_names:
912 name_base = name_full.split('.',1)[0]
826 name_base = name_full.split('.', 1)[0]
913 if name_base in frame.f_code.co_varnames:
827 if name_base in frame.f_code.co_varnames:
914 if name_base in locals:
828 if name_base in locals:
915 try:
829 try:
916 value = repr(eval(name_full,locals))
830 value = repr(eval(name_full, locals))
917 except:
831 except:
918 value = undefined
832 value = undefined
919 else:
833 else:
@@ -922,69 +836,191 b' class VerboseTB(TBTools):'
922 else:
836 else:
923 if name_base in frame.f_globals:
837 if name_base in frame.f_globals:
924 try:
838 try:
925 value = repr(eval(name_full,frame.f_globals))
839 value = repr(eval(name_full, frame.f_globals))
926 except:
840 except:
927 value = undefined
841 value = undefined
928 else:
842 else:
929 value = undefined
843 value = undefined
930 name = tpl_global_var % name_full
844 name = tpl_global_var % name_full
931 lvals.append(tpl_name_val % (name,value))
845 lvals.append(tpl_name_val % (name, value))
932 if lvals:
846 if lvals:
933 lvals = '%s%s' % (indent,em_normal.join(lvals))
847 lvals = '%s%s' % (indent, em_normal.join(lvals))
934 else:
848 else:
935 lvals = ''
849 lvals = ''
936
850
937 level = '%s %s\n' % (link,call)
851 level = '%s %s\n' % (link, call)
938
852
939 if index is None:
853 if index is None:
940 frames.append(level)
854 frames.append(level)
941 else:
855 else:
942 frames.append('%s%s' % (level,''.join(
856 frames.append('%s%s' % (level, ''.join(
943 _format_traceback_lines(lnum,index,lines,Colors,lvals,
857 _format_traceback_lines(lnum, index, lines, Colors, lvals,
944 col_scheme))))
858 col_scheme))))
945
859
860 return frames
861
862 def prepare_chained_exception_message(self, cause):
863 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
864 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
865
866 if cause:
867 message = [[direct_cause]]
868 else:
869 message = [[exception_during_handling]]
870 return message
871
872 def prepare_header(self, etype, long_version=False):
873 colors = self.Colors # just a shorthand + quicker name lookup
874 colorsnormal = colors.Normal # used a lot
875 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
876 if long_version:
877 # Header with the exception type, python version, and date
878 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
879 date = time.ctime(time.time())
880
881 head = '%s%s%s\n%s%s%s\n%s' % (colors.topline, '-' * 75, colorsnormal,
882 exc, ' ' * (75 - len(str(etype)) - len(pyver)),
883 pyver, date.rjust(75) )
884 head += "\nA problem occurred executing Python code. Here is the sequence of function" \
885 "\ncalls leading up to the error, with the most recent (innermost) call last."
886 else:
887 # Simplified header
888 head = '%s%s' % (exc, 'Traceback (most recent call last)'. \
889 rjust(75 - len(str(etype))) )
890
891 return head
892
893 def format_exception(self, etype, evalue):
894 colors = self.Colors # just a shorthand + quicker name lookup
895 colorsnormal = colors.Normal # used a lot
896 indent = ' ' * INDENT_SIZE
946 # Get (safely) a string form of the exception info
897 # Get (safely) a string form of the exception info
947 try:
898 try:
948 etype_str,evalue_str = map(str,(etype,evalue))
899 etype_str, evalue_str = map(str, (etype, evalue))
949 except:
900 except:
950 # User exception is improperly defined.
901 # User exception is improperly defined.
951 etype,evalue = str,sys.exc_info()[:2]
902 etype, evalue = str, sys.exc_info()[:2]
952 etype_str,evalue_str = map(str,(etype,evalue))
903 etype_str, evalue_str = map(str, (etype, evalue))
953 # ... and format it
904 # ... and format it
954 exception = ['%s%s%s: %s' % (Colors.excName, etype_str,
905 exception = ['%s%s%s: %s' % (colors.excName, etype_str,
955 ColorsNormal, py3compat.cast_unicode(evalue_str))]
906 colorsnormal, py3compat.cast_unicode(evalue_str))]
907
956 if (not py3compat.PY3) and type(evalue) is types.InstanceType:
908 if (not py3compat.PY3) and type(evalue) is types.InstanceType:
957 try:
909 try:
958 names = [w for w in dir(evalue) if isinstance(w, py3compat.string_types)]
910 names = [w for w in dir(evalue) if isinstance(w, py3compat.string_types)]
959 except:
911 except:
960 # Every now and then, an object with funny inernals blows up
912 # Every now and then, an object with funny internals blows up
961 # when dir() is called on it. We do the best we can to report
913 # when dir() is called on it. We do the best we can to report
962 # the problem and continue
914 # the problem and continue
963 _m = '%sException reporting error (object with broken dir())%s:'
915 _m = '%sException reporting error (object with broken dir())%s:'
964 exception.append(_m % (Colors.excName,ColorsNormal))
916 exception.append(_m % (colors.excName, colorsnormal))
965 etype_str,evalue_str = map(str,sys.exc_info()[:2])
917 etype_str, evalue_str = map(str, sys.exc_info()[:2])
966 exception.append('%s%s%s: %s' % (Colors.excName,etype_str,
918 exception.append('%s%s%s: %s' % (colors.excName, etype_str,
967 ColorsNormal, py3compat.cast_unicode(evalue_str)))
919 colorsnormal, py3compat.cast_unicode(evalue_str)))
968 names = []
920 names = []
969 for name in names:
921 for name in names:
970 value = text_repr(getattr(evalue, name))
922 value = text_repr(getattr(evalue, name))
971 exception.append('\n%s%s = %s' % (indent, name, value))
923 exception.append('\n%s%s = %s' % (indent, name, value))
972
924
973 # vds: >>
925 return exception
926
927 def format_exception_as_a_whole(self, etype, evalue, etb, number_of_lines_of_context, tb_offset):
928 # some locals
929 try:
930 etype = etype.__name__
931 except AttributeError:
932 pass
933
934 tb_offset = self.tb_offset if tb_offset is None else tb_offset
935 head = self.prepare_header(etype, self.long_header)
936 records = self.get_records(etb, number_of_lines_of_context, tb_offset)
937
938 frames = self.format_records(records)
939 if records is None:
940 return ""
941
942 formatted_exception = self.format_exception(etype, evalue)
974 if records:
943 if records:
975 filepath, lnum = records[-1][1:3]
944 filepath, lnum = records[-1][1:3]
976 #print "file:", str(file), "linenb", str(lnum) # dbg
945 filepath = os.path.abspath(filepath)
977 filepath = os.path.abspath(filepath)
946 ipinst = get_ipython()
978 ipinst = get_ipython()
947 if ipinst is not None:
979 if ipinst is not None:
948 ipinst.hooks.synchronize_with_editor(filepath, lnum, 0)
980 ipinst.hooks.synchronize_with_editor(filepath, lnum, 0)
949
981 # vds: <<
950 return [[head] + frames + [''.join(formatted_exception[0])]]
982
951
983 # return all our info assembled as a single string
952 def get_records(self, etb, number_of_lines_of_context, tb_offset):
984 # return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) )
953 try:
985 return [head] + frames + [''.join(exception[0])]
954 # Try the default getinnerframes and Alex's: Alex's fixes some
986
955 # problems, but it generates empty tracebacks for console errors
987 def debugger(self,force=False):
956 # (5 blanks lines) where none should be returned.
957 return _fixed_getinnerframes(etb, number_of_lines_of_context, tb_offset)
958 except:
959 # FIXME: I've been getting many crash reports from python 2.3
960 # users, traceable to inspect.py. If I can find a small test-case
961 # to reproduce this, I should either write a better workaround or
962 # file a bug report against inspect (if that's the real problem).
963 # So far, I haven't been able to find an isolated example to
964 # reproduce the problem.
965 inspect_error()
966 traceback.print_exc(file=self.ostream)
967 info('\nUnfortunately, your original traceback can not be constructed.\n')
968 return None
969
970 def get_parts_of_chained_exception(self, evalue):
971 def get_chained_exception(exception_value):
972 cause = getattr(exception_value, '__cause__', None)
973 if cause:
974 return cause
975 return getattr(exception_value, '__context__', None)
976
977 chained_evalue = get_chained_exception(evalue)
978
979 if chained_evalue:
980 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
981
982 def structured_traceback(self, etype, evalue, etb, tb_offset=None,
983 number_of_lines_of_context=5):
984 """Return a nice text document describing the traceback."""
985
986 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
987 tb_offset)
988
989 colors = self.Colors # just a shorthand + quicker name lookup
990 colorsnormal = colors.Normal # used a lot
991 head = '%s%s%s' % (colors.topline, '-' * 75, colorsnormal)
992 structured_traceback_parts = [head]
993 if py3compat.PY3:
994 chained_exceptions_tb_offset = 0
995 lines_of_context = 3
996 formatted_exceptions = formatted_exception
997 exception = self.get_parts_of_chained_exception(evalue)
998 if exception:
999 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1000 etype, evalue, etb = exception
1001 else:
1002 evalue = None
1003 while evalue:
1004 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1005 chained_exceptions_tb_offset)
1006 exception = self.get_parts_of_chained_exception(evalue)
1007
1008 if exception:
1009 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1010 etype, evalue, etb = exception
1011 else:
1012 evalue = None
1013
1014 # we want to see exceptions in a reversed order:
1015 # the first exception should be on top
1016 for formatted_exception in reversed(formatted_exceptions):
1017 structured_traceback_parts += formatted_exception
1018 else:
1019 structured_traceback_parts += formatted_exception[0]
1020
1021 return structured_traceback_parts
1022
1023 def debugger(self, force=False):
988 """Call up the pdb debugger if desired, always clean up the tb
1024 """Call up the pdb debugger if desired, always clean up the tb
989 reference.
1025 reference.
990
1026
@@ -1014,7 +1050,7 b' class VerboseTB(TBTools):'
1014 with display_trap:
1050 with display_trap:
1015 self.pdb.reset()
1051 self.pdb.reset()
1016 # Find the right frame so we don't pop up inside ipython itself
1052 # Find the right frame so we don't pop up inside ipython itself
1017 if hasattr(self,'tb') and self.tb is not None:
1053 if hasattr(self, 'tb') and self.tb is not None:
1018 etb = self.tb
1054 etb = self.tb
1019 else:
1055 else:
1020 etb = self.tb = sys.last_traceback
1056 etb = self.tb = sys.last_traceback
@@ -1025,7 +1061,7 b' class VerboseTB(TBTools):'
1025 self.pdb.botframe = etb.tb_frame
1061 self.pdb.botframe = etb.tb_frame
1026 self.pdb.interaction(self.tb.tb_frame, self.tb)
1062 self.pdb.interaction(self.tb.tb_frame, self.tb)
1027
1063
1028 if hasattr(self,'tb'):
1064 if hasattr(self, 'tb'):
1029 del self.tb
1065 del self.tb
1030
1066
1031 def handler(self, info=None):
1067 def handler(self, info=None):
@@ -1050,6 +1086,7 b' class VerboseTB(TBTools):'
1050 except KeyboardInterrupt:
1086 except KeyboardInterrupt:
1051 print("\nKeyboardInterrupt")
1087 print("\nKeyboardInterrupt")
1052
1088
1089
1053 #----------------------------------------------------------------------------
1090 #----------------------------------------------------------------------------
1054 class FormattedTB(VerboseTB, ListTB):
1091 class FormattedTB(VerboseTB, ListTB):
1055 """Subclass ListTB but allow calling with a traceback.
1092 """Subclass ListTB but allow calling with a traceback.
@@ -1069,7 +1106,7 b' class FormattedTB(VerboseTB, ListTB):'
1069 check_cache=None):
1106 check_cache=None):
1070
1107
1071 # NEVER change the order of this list. Put new modes at the end:
1108 # NEVER change the order of this list. Put new modes at the end:
1072 self.valid_modes = ['Plain','Context','Verbose']
1109 self.valid_modes = ['Plain', 'Context', 'Verbose']
1073 self.verbose_modes = self.valid_modes[1:3]
1110 self.verbose_modes = self.valid_modes[1:3]
1074
1111
1075 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1112 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
@@ -1083,19 +1120,19 b' class FormattedTB(VerboseTB, ListTB):'
1083 # set_mode also sets the tb_join_char attribute
1120 # set_mode also sets the tb_join_char attribute
1084 self.set_mode(mode)
1121 self.set_mode(mode)
1085
1122
1086 def _extract_tb(self,tb):
1123 def _extract_tb(self, tb):
1087 if tb:
1124 if tb:
1088 return traceback.extract_tb(tb)
1125 return traceback.extract_tb(tb)
1089 else:
1126 else:
1090 return None
1127 return None
1091
1128
1092 def structured_traceback(self, etype, value, tb, tb_offset=None, context=5):
1129 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1093 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1130 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1094 mode = self.mode
1131 mode = self.mode
1095 if mode in self.verbose_modes:
1132 if mode in self.verbose_modes:
1096 # Verbose modes need a full traceback
1133 # Verbose modes need a full traceback
1097 return VerboseTB.structured_traceback(
1134 return VerboseTB.structured_traceback(
1098 self, etype, value, tb, tb_offset, context
1135 self, etype, value, tb, tb_offset, number_of_lines_of_context
1099 )
1136 )
1100 else:
1137 else:
1101 # We must check the source cache because otherwise we can print
1138 # We must check the source cache because otherwise we can print
@@ -1104,7 +1141,7 b' class FormattedTB(VerboseTB, ListTB):'
1104 # Now we can extract and format the exception
1141 # Now we can extract and format the exception
1105 elist = self._extract_tb(tb)
1142 elist = self._extract_tb(tb)
1106 return ListTB.structured_traceback(
1143 return ListTB.structured_traceback(
1107 self, etype, value, elist, tb_offset, context
1144 self, etype, value, elist, tb_offset, number_of_lines_of_context
1108 )
1145 )
1109
1146
1110 def stb2text(self, stb):
1147 def stb2text(self, stb):
@@ -1112,18 +1149,18 b' class FormattedTB(VerboseTB, ListTB):'
1112 return self.tb_join_char.join(stb)
1149 return self.tb_join_char.join(stb)
1113
1150
1114
1151
1115 def set_mode(self,mode=None):
1152 def set_mode(self, mode=None):
1116 """Switch to the desired mode.
1153 """Switch to the desired mode.
1117
1154
1118 If mode is not specified, cycles through the available modes."""
1155 If mode is not specified, cycles through the available modes."""
1119
1156
1120 if not mode:
1157 if not mode:
1121 new_idx = ( self.valid_modes.index(self.mode) + 1 ) % \
1158 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1122 len(self.valid_modes)
1159 len(self.valid_modes)
1123 self.mode = self.valid_modes[new_idx]
1160 self.mode = self.valid_modes[new_idx]
1124 elif mode not in self.valid_modes:
1161 elif mode not in self.valid_modes:
1125 raise ValueError('Unrecognized mode in FormattedTB: <'+mode+'>\n'
1162 raise ValueError('Unrecognized mode in FormattedTB: <' + mode + '>\n'
1126 'Valid modes: '+str(self.valid_modes))
1163 'Valid modes: ' + str(self.valid_modes))
1127 else:
1164 else:
1128 self.mode = mode
1165 self.mode = mode
1129 # include variable details only in 'Verbose' mode
1166 # include variable details only in 'Verbose' mode
@@ -1131,7 +1168,7 b' class FormattedTB(VerboseTB, ListTB):'
1131 # Set the join character for generating text tracebacks
1168 # Set the join character for generating text tracebacks
1132 self.tb_join_char = self._join_chars[self.mode]
1169 self.tb_join_char = self._join_chars[self.mode]
1133
1170
1134 # some convenient shorcuts
1171 # some convenient shortcuts
1135 def plain(self):
1172 def plain(self):
1136 self.set_mode(self.valid_modes[0])
1173 self.set_mode(self.valid_modes[0])
1137
1174
@@ -1141,6 +1178,7 b' class FormattedTB(VerboseTB, ListTB):'
1141 def verbose(self):
1178 def verbose(self):
1142 self.set_mode(self.valid_modes[2])
1179 self.set_mode(self.valid_modes[2])
1143
1180
1181
1144 #----------------------------------------------------------------------------
1182 #----------------------------------------------------------------------------
1145 class AutoFormattedTB(FormattedTB):
1183 class AutoFormattedTB(FormattedTB):
1146 """A traceback printer which can be called on the fly.
1184 """A traceback printer which can be called on the fly.
@@ -1156,8 +1194,8 b' class AutoFormattedTB(FormattedTB):'
1156 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1194 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1157 """
1195 """
1158
1196
1159 def __call__(self,etype=None,evalue=None,etb=None,
1197 def __call__(self, etype=None, evalue=None, etb=None,
1160 out=None,tb_offset=None):
1198 out=None, tb_offset=None):
1161 """Print out a formatted exception traceback.
1199 """Print out a formatted exception traceback.
1162
1200
1163 Optional arguments:
1201 Optional arguments:
@@ -1167,7 +1205,6 b' class AutoFormattedTB(FormattedTB):'
1167 per-call basis (this overrides temporarily the instance's tb_offset
1205 per-call basis (this overrides temporarily the instance's tb_offset
1168 given at initialization time. """
1206 given at initialization time. """
1169
1207
1170
1171 if out is None:
1208 if out is None:
1172 out = self.ostream
1209 out = self.ostream
1173 out.flush()
1210 out.flush()
@@ -1182,33 +1219,36 b' class AutoFormattedTB(FormattedTB):'
1182 print("\nKeyboardInterrupt")
1219 print("\nKeyboardInterrupt")
1183
1220
1184 def structured_traceback(self, etype=None, value=None, tb=None,
1221 def structured_traceback(self, etype=None, value=None, tb=None,
1185 tb_offset=None, context=5):
1222 tb_offset=None, number_of_lines_of_context=5):
1186 if etype is None:
1223 if etype is None:
1187 etype,value,tb = sys.exc_info()
1224 etype, value, tb = sys.exc_info()
1188 self.tb = tb
1225 self.tb = tb
1189 return FormattedTB.structured_traceback(
1226 return FormattedTB.structured_traceback(
1190 self, etype, value, tb, tb_offset, context)
1227 self, etype, value, tb, tb_offset, number_of_lines_of_context)
1228
1191
1229
1192 #---------------------------------------------------------------------------
1230 #---------------------------------------------------------------------------
1193
1231
1194 # A simple class to preserve Nathan's original functionality.
1232 # A simple class to preserve Nathan's original functionality.
1195 class ColorTB(FormattedTB):
1233 class ColorTB(FormattedTB):
1196 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1234 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1197 def __init__(self,color_scheme='Linux',call_pdb=0):
1235
1198 FormattedTB.__init__(self,color_scheme=color_scheme,
1236 def __init__(self, color_scheme='Linux', call_pdb=0):
1237 FormattedTB.__init__(self, color_scheme=color_scheme,
1199 call_pdb=call_pdb)
1238 call_pdb=call_pdb)
1200
1239
1201
1240
1202 class SyntaxTB(ListTB):
1241 class SyntaxTB(ListTB):
1203 """Extension which holds some state: the last exception value"""
1242 """Extension which holds some state: the last exception value"""
1204
1243
1205 def __init__(self,color_scheme = 'NoColor'):
1244 def __init__(self, color_scheme='NoColor'):
1206 ListTB.__init__(self,color_scheme)
1245 ListTB.__init__(self, color_scheme)
1207 self.last_syntax_error = None
1246 self.last_syntax_error = None
1208
1247
1209 def __call__(self, etype, value, elist):
1248 def __call__(self, etype, value, elist):
1210 self.last_syntax_error = value
1249 self.last_syntax_error = value
1211 ListTB.__call__(self,etype,value,elist)
1250
1251 ListTB.__call__(self, etype, value, elist)
1212
1252
1213 def structured_traceback(self, etype, value, elist, tb_offset=None,
1253 def structured_traceback(self, etype, value, elist, tb_offset=None,
1214 context=5):
1254 context=5):
@@ -1223,7 +1263,7 b' class SyntaxTB(ListTB):'
1223 if newtext:
1263 if newtext:
1224 value.text = newtext
1264 value.text = newtext
1225 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1265 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1226 tb_offset=tb_offset, context=context)
1266 tb_offset=tb_offset, context=context)
1227
1267
1228 def clear_err_state(self):
1268 def clear_err_state(self):
1229 """Return the current error state and clear it"""
1269 """Return the current error state and clear it"""
@@ -1236,7 +1276,46 b' class SyntaxTB(ListTB):'
1236 return ''.join(stb)
1276 return ''.join(stb)
1237
1277
1238
1278
1279 # some internal-use functions
1280 def text_repr(value):
1281 """Hopefully pretty robust repr equivalent."""
1282 # this is pretty horrible but should always return *something*
1283 try:
1284 return pydoc.text.repr(value)
1285 except KeyboardInterrupt:
1286 raise
1287 except:
1288 try:
1289 return repr(value)
1290 except KeyboardInterrupt:
1291 raise
1292 except:
1293 try:
1294 # all still in an except block so we catch
1295 # getattr raising
1296 name = getattr(value, '__name__', None)
1297 if name:
1298 # ick, recursion
1299 return text_repr(name)
1300 klass = getattr(value, '__class__', None)
1301 if klass:
1302 return '%s instance' % text_repr(klass)
1303 except KeyboardInterrupt:
1304 raise
1305 except:
1306 return 'UNRECOVERABLE REPR FAILURE'
1307
1308
1309 def eqrepr(value, repr=text_repr):
1310 return '=%s' % repr(value)
1311
1312
1313 def nullrepr(value, repr=text_repr):
1314 return ''
1315
1316
1239 #----------------------------------------------------------------------------
1317 #----------------------------------------------------------------------------
1318
1240 # module testing (minimal)
1319 # module testing (minimal)
1241 if __name__ == "__main__":
1320 if __name__ == "__main__":
1242 def spam(c, d_e):
1321 def spam(c, d_e):
@@ -136,7 +136,7 b' def extract_zip(fd, dest):'
136 os.rename(os.path.join(parent, topdir), dest)
136 os.rename(os.path.join(parent, topdir), dest)
137
137
138
138
139 def install_mathjax(tag='v2.2', dest=default_dest, replace=False, file=None, extractor=extract_tar):
139 def install_mathjax(tag='2.4.0', dest=default_dest, replace=False, file=None, extractor=extract_tar):
140 """Download and/or install MathJax for offline use.
140 """Download and/or install MathJax for offline use.
141
141
142 This will install mathjax to the nbextensions dir in your IPYTHONDIR.
142 This will install mathjax to the nbextensions dir in your IPYTHONDIR.
@@ -150,8 +150,8 b" def install_mathjax(tag='v2.2', dest=default_dest, replace=False, file=None, ext"
150 Whether to remove and replace an existing install.
150 Whether to remove and replace an existing install.
151 dest : str [IPYTHONDIR/nbextensions/mathjax]
151 dest : str [IPYTHONDIR/nbextensions/mathjax]
152 Where to install mathjax
152 Where to install mathjax
153 tag : str ['v2.2']
153 tag : str ['2.4.0']
154 Which tag to download. Default is 'v2.2', the current stable release,
154 Which tag to download. Default is '2.4.0', the current stable release,
155 but alternatives include 'v1.1a' and 'master'.
155 but alternatives include 'v1.1a' and 'master'.
156 file : file like object [ defualt to content of https://github.com/mathjax/MathJax/tarball/#{tag}]
156 file : file like object [ defualt to content of https://github.com/mathjax/MathJax/tarball/#{tag}]
157 File handle from which to untar/unzip/... mathjax
157 File handle from which to untar/unzip/... mathjax
@@ -80,6 +80,7 b' try:'
80 import traceback
80 import traceback
81 import signal
81 import signal
82 import codecs
82 import codecs
83 import stat
83 except ImportError: # pragma: no cover
84 except ImportError: # pragma: no cover
84 err = sys.exc_info()[1]
85 err = sys.exc_info()[1]
85 raise ImportError(str(err) + '''
86 raise ImportError(str(err) + '''
@@ -87,7 +88,7 b' except ImportError: # pragma: no cover'
87 A critical module was not found. Probably this operating system does not
88 A critical module was not found. Probably this operating system does not
88 support it. Pexpect is intended for UNIX-like operating systems.''')
89 support it. Pexpect is intended for UNIX-like operating systems.''')
89
90
90 __version__ = '3.2'
91 __version__ = '3.3'
91 __revision__ = ''
92 __revision__ = ''
92 __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
93 __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
93 'which', 'split_command_line', '__version__', '__revision__']
94 'which', 'split_command_line', '__version__', '__revision__']
@@ -284,6 +285,7 b' class spawn(object):'
284 def _chr(c):
285 def _chr(c):
285 return bytes([c])
286 return bytes([c])
286 linesep = os.linesep.encode('ascii')
287 linesep = os.linesep.encode('ascii')
288 crlf = '\r\n'.encode('ascii')
287
289
288 @staticmethod
290 @staticmethod
289 def write_to_stdout(b):
291 def write_to_stdout(b):
@@ -296,13 +298,14 b' class spawn(object):'
296 allowed_string_types = (basestring,) # analysis:ignore
298 allowed_string_types = (basestring,) # analysis:ignore
297 _chr = staticmethod(chr)
299 _chr = staticmethod(chr)
298 linesep = os.linesep
300 linesep = os.linesep
301 crlf = '\r\n'
299 write_to_stdout = sys.stdout.write
302 write_to_stdout = sys.stdout.write
300
303
301 encoding = None
304 encoding = None
302
305
303 def __init__(self, command, args=[], timeout=30, maxread=2000,
306 def __init__(self, command, args=[], timeout=30, maxread=2000,
304 searchwindowsize=None, logfile=None, cwd=None, env=None,
307 searchwindowsize=None, logfile=None, cwd=None, env=None,
305 ignore_sighup=True):
308 ignore_sighup=True, echo=True):
306
309
307 '''This is the constructor. The command parameter may be a string that
310 '''This is the constructor. The command parameter may be a string that
308 includes a command and any arguments to the command. For example::
311 includes a command and any arguments to the command. For example::
@@ -415,7 +418,16 b' class spawn(object):'
415 signalstatus will store the signal value and exitstatus will be None.
418 signalstatus will store the signal value and exitstatus will be None.
416 If you need more detail you can also read the self.status member which
419 If you need more detail you can also read the self.status member which
417 stores the status returned by os.waitpid. You can interpret this using
420 stores the status returned by os.waitpid. You can interpret this using
418 os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. '''
421 os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG.
422
423 The echo attribute may be set to False to disable echoing of input.
424 As a pseudo-terminal, all input echoed by the "keyboard" (send()
425 or sendline()) will be repeated to output. For many cases, it is
426 not desirable to have echo enabled, and it may be later disabled
427 using setecho(False) followed by waitnoecho(). However, for some
428 platforms such as Solaris, this is not possible, and should be
429 disabled immediately on spawn.
430 '''
419
431
420 self.STDIN_FILENO = pty.STDIN_FILENO
432 self.STDIN_FILENO = pty.STDIN_FILENO
421 self.STDOUT_FILENO = pty.STDOUT_FILENO
433 self.STDOUT_FILENO = pty.STDOUT_FILENO
@@ -437,7 +449,7 b' class spawn(object):'
437 self.status = None
449 self.status = None
438 self.flag_eof = False
450 self.flag_eof = False
439 self.pid = None
451 self.pid = None
440 # the chile filedescriptor is initially closed
452 # the child file descriptor is initially closed
441 self.child_fd = -1
453 self.child_fd = -1
442 self.timeout = timeout
454 self.timeout = timeout
443 self.delimiter = EOF
455 self.delimiter = EOF
@@ -466,16 +478,30 b' class spawn(object):'
466 self.closed = True
478 self.closed = True
467 self.cwd = cwd
479 self.cwd = cwd
468 self.env = env
480 self.env = env
481 self.echo = echo
469 self.ignore_sighup = ignore_sighup
482 self.ignore_sighup = ignore_sighup
483 _platform = sys.platform.lower()
470 # This flags if we are running on irix
484 # This flags if we are running on irix
471 self.__irix_hack = (sys.platform.lower().find('irix') >= 0)
485 self.__irix_hack = _platform.startswith('irix')
472 # Solaris uses internal __fork_pty(). All others use pty.fork().
486 # Solaris uses internal __fork_pty(). All others use pty.fork().
473 if ((sys.platform.lower().find('solaris') >= 0)
487 self.use_native_pty_fork = not (
474 or (sys.platform.lower().find('sunos5') >= 0)):
488 _platform.startswith('solaris') or
475 self.use_native_pty_fork = False
489 _platform.startswith('sunos'))
476 else:
490 # inherit EOF and INTR definitions from controlling process.
477 self.use_native_pty_fork = True
491 try:
478
492 from termios import VEOF, VINTR
493 fd = sys.__stdin__.fileno()
494 self._INTR = ord(termios.tcgetattr(fd)[6][VINTR])
495 self._EOF = ord(termios.tcgetattr(fd)[6][VEOF])
496 except (ImportError, OSError, IOError, termios.error):
497 # unless the controlling process is also not a terminal,
498 # such as cron(1). Fall-back to using CEOF and CINTR.
499 try:
500 from termios import CEOF, CINTR
501 (self._INTR, self._EOF) = (CINTR, CEOF)
502 except ImportError:
503 # ^C, ^D
504 (self._INTR, self._EOF) = (3, 4)
479 # Support subclasses that do not use command or args.
505 # Support subclasses that do not use command or args.
480 if command is None:
506 if command is None:
481 self.command = None
507 self.command = None
@@ -599,33 +625,39 b' class spawn(object):'
599 if self.use_native_pty_fork:
625 if self.use_native_pty_fork:
600 try:
626 try:
601 self.pid, self.child_fd = pty.fork()
627 self.pid, self.child_fd = pty.fork()
602 except OSError:
628 except OSError: # pragma: no cover
603 err = sys.exc_info()[1]
629 err = sys.exc_info()[1]
604 raise ExceptionPexpect('pty.fork() failed: ' + str(err))
630 raise ExceptionPexpect('pty.fork() failed: ' + str(err))
605 else:
631 else:
606 # Use internal __fork_pty
632 # Use internal __fork_pty
607 self.pid, self.child_fd = self.__fork_pty()
633 self.pid, self.child_fd = self.__fork_pty()
608
634
609 if self.pid == 0:
635 # Some platforms must call setwinsize() and setecho() from the
636 # child process, and others from the master process. We do both,
637 # allowing IOError for either.
638
639 if self.pid == pty.CHILD:
610 # Child
640 # Child
641 self.child_fd = self.STDIN_FILENO
642
643 # set default window size of 24 rows by 80 columns
611 try:
644 try:
612 # used by setwinsize()
613 self.child_fd = sys.stdout.fileno()
614 self.setwinsize(24, 80)
645 self.setwinsize(24, 80)
615 # which exception, shouldnt' we catch explicitly .. ?
646 except IOError as err:
616 except:
647 if err.args[0] not in (errno.EINVAL, errno.ENOTTY):
617 # Some platforms do not like setwinsize (Cygwin).
648 raise
618 # This will cause problem when running applications that
649
619 # are very picky about window size.
650 # disable echo if spawn argument echo was unset
620 # This is a serious limitation, but not a show stopper.
651 if not self.echo:
621 pass
652 try:
653 self.setecho(self.echo)
654 except (IOError, termios.error) as err:
655 if err.args[0] not in (errno.EINVAL, errno.ENOTTY):
656 raise
657
622 # Do not allow child to inherit open file descriptors from parent.
658 # Do not allow child to inherit open file descriptors from parent.
623 max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
659 max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
624 for i in range(3, max_fd):
660 os.closerange(3, max_fd)
625 try:
626 os.close(i)
627 except OSError:
628 pass
629
661
630 if self.ignore_sighup:
662 if self.ignore_sighup:
631 signal.signal(signal.SIGHUP, signal.SIG_IGN)
663 signal.signal(signal.SIGHUP, signal.SIG_IGN)
@@ -638,6 +670,13 b' class spawn(object):'
638 os.execvpe(self.command, self.args, self.env)
670 os.execvpe(self.command, self.args, self.env)
639
671
640 # Parent
672 # Parent
673 try:
674 self.setwinsize(24, 80)
675 except IOError as err:
676 if err.args[0] not in (errno.EINVAL, errno.ENOTTY):
677 raise
678
679
641 self.terminated = False
680 self.terminated = False
642 self.closed = False
681 self.closed = False
643
682
@@ -660,19 +699,15 b' class spawn(object):'
660 raise ExceptionPexpect("Could not open with os.openpty().")
699 raise ExceptionPexpect("Could not open with os.openpty().")
661
700
662 pid = os.fork()
701 pid = os.fork()
663 if pid < 0:
702 if pid == pty.CHILD:
664 raise ExceptionPexpect("Failed os.fork().")
665 elif pid == 0:
666 # Child.
703 # Child.
667 os.close(parent_fd)
704 os.close(parent_fd)
668 self.__pty_make_controlling_tty(child_fd)
705 self.__pty_make_controlling_tty(child_fd)
669
706
670 os.dup2(child_fd, 0)
707 os.dup2(child_fd, self.STDIN_FILENO)
671 os.dup2(child_fd, 1)
708 os.dup2(child_fd, self.STDOUT_FILENO)
672 os.dup2(child_fd, 2)
709 os.dup2(child_fd, self.STDERR_FILENO)
673
710
674 if child_fd > 2:
675 os.close(child_fd)
676 else:
711 else:
677 # Parent.
712 # Parent.
678 os.close(child_fd)
713 os.close(child_fd)
@@ -686,44 +721,36 b' class spawn(object):'
686
721
687 child_name = os.ttyname(tty_fd)
722 child_name = os.ttyname(tty_fd)
688
723
689 # Disconnect from controlling tty. Harmless if not already connected.
724 # Disconnect from controlling tty, if any. Raises OSError of ENXIO
725 # if there was no controlling tty to begin with, such as when
726 # executed by a cron(1) job.
690 try:
727 try:
691 fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
728 fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
692 if fd >= 0:
729 os.close(fd)
693 os.close(fd)
730 except OSError as err:
694 # which exception, shouldnt' we catch explicitly .. ?
731 if err.errno != errno.ENXIO:
695 except:
732 raise
696 # Already disconnected. This happens if running inside cron.
697 pass
698
733
699 os.setsid()
734 os.setsid()
700
735
701 # Verify we are disconnected from controlling tty
736 # Verify we are disconnected from controlling tty by attempting to open
702 # by attempting to open it again.
737 # it again. We expect that OSError of ENXIO should always be raised.
703 try:
738 try:
704 fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
739 fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
705 if fd >= 0:
740 os.close(fd)
706 os.close(fd)
741 raise ExceptionPexpect("OSError of errno.ENXIO should be raised.")
707 raise ExceptionPexpect('Failed to disconnect from ' +
742 except OSError as err:
708 'controlling tty. It is still possible to open /dev/tty.')
743 if err.errno != errno.ENXIO:
709 # which exception, shouldnt' we catch explicitly .. ?
744 raise
710 except:
711 # Good! We are disconnected from a controlling tty.
712 pass
713
745
714 # Verify we can open child pty.
746 # Verify we can open child pty.
715 fd = os.open(child_name, os.O_RDWR)
747 fd = os.open(child_name, os.O_RDWR)
716 if fd < 0:
748 os.close(fd)
717 raise ExceptionPexpect("Could not open child pty, " + child_name)
718 else:
719 os.close(fd)
720
749
721 # Verify we now have a controlling tty.
750 # Verify we now have a controlling tty.
722 fd = os.open("/dev/tty", os.O_WRONLY)
751 fd = os.open("/dev/tty", os.O_WRONLY)
723 if fd < 0:
752 os.close(fd)
724 raise ExceptionPexpect("Could not open controlling tty, /dev/tty")
753
725 else:
726 os.close(fd)
727
754
728 def fileno(self):
755 def fileno(self):
729 '''This returns the file descriptor of the pty for the child.
756 '''This returns the file descriptor of the pty for the child.
@@ -757,7 +784,12 b' class spawn(object):'
757
784
758 def isatty(self):
785 def isatty(self):
759 '''This returns True if the file descriptor is open and connected to a
786 '''This returns True if the file descriptor is open and connected to a
760 tty(-like) device, else False. '''
787 tty(-like) device, else False.
788
789 On SVR4-style platforms implementing streams, such as SunOS and HP-UX,
790 the child pty may not appear as a terminal device. This means
791 methods such as setecho(), setwinsize(), getwinsize() may raise an
792 IOError. '''
761
793
762 return os.isatty(self.child_fd)
794 return os.isatty(self.child_fd)
763
795
@@ -794,12 +826,20 b' class spawn(object):'
794 def getecho(self):
826 def getecho(self):
795 '''This returns the terminal echo mode. This returns True if echo is
827 '''This returns the terminal echo mode. This returns True if echo is
796 on or False if echo is off. Child applications that are expecting you
828 on or False if echo is off. Child applications that are expecting you
797 to enter a password often set ECHO False. See waitnoecho(). '''
829 to enter a password often set ECHO False. See waitnoecho().
798
830
799 attr = termios.tcgetattr(self.child_fd)
831 Not supported on platforms where ``isatty()`` returns False. '''
800 if attr[3] & termios.ECHO:
832
801 return True
833 try:
802 return False
834 attr = termios.tcgetattr(self.child_fd)
835 except termios.error as err:
836 errmsg = 'getecho() may not be called on this platform'
837 if err.args[0] == errno.EINVAL:
838 raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
839 raise
840
841 self.echo = bool(attr[3] & termios.ECHO)
842 return self.echo
803
843
804 def setecho(self, state):
844 def setecho(self, state):
805 '''This sets the terminal echo mode on or off. Note that anything the
845 '''This sets the terminal echo mode on or off. Note that anything the
@@ -829,18 +869,35 b' class spawn(object):'
829 p.expect(['1234'])
869 p.expect(['1234'])
830 p.expect(['abcd'])
870 p.expect(['abcd'])
831 p.expect(['wxyz'])
871 p.expect(['wxyz'])
872
873
874 Not supported on platforms where ``isatty()`` returns False.
832 '''
875 '''
833
876
834 self.child_fd
877 errmsg = 'setecho() may not be called on this platform'
835 attr = termios.tcgetattr(self.child_fd)
878
879 try:
880 attr = termios.tcgetattr(self.child_fd)
881 except termios.error as err:
882 if err.args[0] == errno.EINVAL:
883 raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
884 raise
885
836 if state:
886 if state:
837 attr[3] = attr[3] | termios.ECHO
887 attr[3] = attr[3] | termios.ECHO
838 else:
888 else:
839 attr[3] = attr[3] & ~termios.ECHO
889 attr[3] = attr[3] & ~termios.ECHO
840 # I tried TCSADRAIN and TCSAFLUSH, but
890
841 # these were inconsistent and blocked on some platforms.
891 try:
842 # TCSADRAIN would probably be ideal if it worked.
892 # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and
843 termios.tcsetattr(self.child_fd, termios.TCSANOW, attr)
893 # blocked on some platforms. TCSADRAIN would probably be ideal.
894 termios.tcsetattr(self.child_fd, termios.TCSANOW, attr)
895 except IOError as err:
896 if err.args[0] == errno.EINVAL:
897 raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
898 raise
899
900 self.echo = state
844
901
845 def _log(self, s, direction):
902 def _log(self, s, direction):
846 if self.logfile is not None:
903 if self.logfile is not None:
@@ -913,12 +970,14 b' class spawn(object):'
913 if self.child_fd in r:
970 if self.child_fd in r:
914 try:
971 try:
915 s = os.read(self.child_fd, size)
972 s = os.read(self.child_fd, size)
916 except OSError:
973 except OSError as err:
917 # Linux does this
974 if err.args[0] == errno.EIO:
918 self.flag_eof = True
975 # Linux-style EOF
919 raise EOF('End Of File (EOF). Exception style platform.')
976 self.flag_eof = True
977 raise EOF('End Of File (EOF). Exception style platform.')
978 raise
920 if s == b'':
979 if s == b'':
921 # BSD style
980 # BSD-style EOF
922 self.flag_eof = True
981 self.flag_eof = True
923 raise EOF('End Of File (EOF). Empty string style platform.')
982 raise EOF('End Of File (EOF). Empty string style platform.')
924
983
@@ -926,7 +985,7 b' class spawn(object):'
926 self._log(s, 'read')
985 self._log(s, 'read')
927 return s
986 return s
928
987
929 raise ExceptionPexpect('Reached an unexpected state.')
988 raise ExceptionPexpect('Reached an unexpected state.') # pragma: no cover
930
989
931 def read(self, size=-1):
990 def read(self, size=-1):
932 '''This reads at most "size" bytes from the file (less if the read hits
991 '''This reads at most "size" bytes from the file (less if the read hits
@@ -972,9 +1031,9 b' class spawn(object):'
972 if size == 0:
1031 if size == 0:
973 return self.string_type()
1032 return self.string_type()
974 # delimiter default is EOF
1033 # delimiter default is EOF
975 index = self.expect([b'\r\n', self.delimiter])
1034 index = self.expect([self.crlf, self.delimiter])
976 if index == 0:
1035 if index == 0:
977 return self.before + b'\r\n'
1036 return self.before + self.crlf
978 else:
1037 else:
979 return self.before
1038 return self.before
980
1039
@@ -1075,40 +1134,14 b' class spawn(object):'
1075 It is the responsibility of the caller to ensure the eof is sent at the
1134 It is the responsibility of the caller to ensure the eof is sent at the
1076 beginning of a line. '''
1135 beginning of a line. '''
1077
1136
1078 ### Hmmm... how do I send an EOF?
1137 self.send(self._chr(self._EOF))
1079 ###C if ((m = write(pty, *buf, p - *buf)) < 0)
1080 ###C return (errno == EWOULDBLOCK) ? n : -1;
1081 #fd = sys.stdin.fileno()
1082 #old = termios.tcgetattr(fd) # remember current state
1083 #attr = termios.tcgetattr(fd)
1084 #attr[3] = attr[3] | termios.ICANON # ICANON must be set to see EOF
1085 #try: # use try/finally to ensure state gets restored
1086 # termios.tcsetattr(fd, termios.TCSADRAIN, attr)
1087 # if hasattr(termios, 'CEOF'):
1088 # os.write(self.child_fd, '%c' % termios.CEOF)
1089 # else:
1090 # # Silly platform does not define CEOF so assume CTRL-D
1091 # os.write(self.child_fd, '%c' % 4)
1092 #finally: # restore state
1093 # termios.tcsetattr(fd, termios.TCSADRAIN, old)
1094 if hasattr(termios, 'VEOF'):
1095 char = ord(termios.tcgetattr(self.child_fd)[6][termios.VEOF])
1096 else:
1097 # platform does not define VEOF so assume CTRL-D
1098 char = 4
1099 self.send(self._chr(char))
1100
1138
1101 def sendintr(self):
1139 def sendintr(self):
1102
1140
1103 '''This sends a SIGINT to the child. It does not require
1141 '''This sends a SIGINT to the child. It does not require
1104 the SIGINT to be the first character on a line. '''
1142 the SIGINT to be the first character on a line. '''
1105
1143
1106 if hasattr(termios, 'VINTR'):
1144 self.send(self._chr(self._INTR))
1107 char = ord(termios.tcgetattr(self.child_fd)[6][termios.VINTR])
1108 else:
1109 # platform does not define VINTR so assume CTRL-C
1110 char = 3
1111 self.send(self._chr(char))
1112
1145
1113 def eof(self):
1146 def eof(self):
1114
1147
@@ -1181,7 +1214,7 b' class spawn(object):'
1181 self.exitstatus = None
1214 self.exitstatus = None
1182 self.signalstatus = os.WTERMSIG(status)
1215 self.signalstatus = os.WTERMSIG(status)
1183 self.terminated = True
1216 self.terminated = True
1184 elif os.WIFSTOPPED(status):
1217 elif os.WIFSTOPPED(status): # pragma: no cover
1185 # You can't call wait() on a child process in the stopped state.
1218 # You can't call wait() on a child process in the stopped state.
1186 raise ExceptionPexpect('Called wait() on a stopped child ' +
1219 raise ExceptionPexpect('Called wait() on a stopped child ' +
1187 'process. This is not supported. Is some other ' +
1220 'process. This is not supported. Is some other ' +
@@ -1201,7 +1234,7 b' class spawn(object):'
1201
1234
1202 if self.flag_eof:
1235 if self.flag_eof:
1203 # This is for Linux, which requires the blocking form
1236 # This is for Linux, which requires the blocking form
1204 # of waitpid to # get status of a defunct process.
1237 # of waitpid to get the status of a defunct process.
1205 # This is super-lame. The flag_eof would have been set
1238 # This is super-lame. The flag_eof would have been set
1206 # in read_nonblocking(), so this should be safe.
1239 # in read_nonblocking(), so this should be safe.
1207 waitpid_options = 0
1240 waitpid_options = 0
@@ -1229,7 +1262,7 b' class spawn(object):'
1229 try:
1262 try:
1230 ### os.WNOHANG) # Solaris!
1263 ### os.WNOHANG) # Solaris!
1231 pid, status = os.waitpid(self.pid, waitpid_options)
1264 pid, status = os.waitpid(self.pid, waitpid_options)
1232 except OSError as e:
1265 except OSError as e: # pragma: no cover
1233 # This should never happen...
1266 # This should never happen...
1234 if e.errno == errno.ECHILD:
1267 if e.errno == errno.ECHILD:
1235 raise ExceptionPexpect('isalive() encountered condition ' +
1268 raise ExceptionPexpect('isalive() encountered condition ' +
@@ -1643,10 +1676,14 b' class spawn(object):'
1643 if self.child_fd in r:
1676 if self.child_fd in r:
1644 try:
1677 try:
1645 data = self.__interact_read(self.child_fd)
1678 data = self.__interact_read(self.child_fd)
1646 except OSError as e:
1679 except OSError as err:
1647 # The subprocess may have closed before we get to reading it
1680 if err.args[0] == errno.EIO:
1648 if e.errno != errno.EIO:
1681 # Linux-style EOF
1649 raise
1682 break
1683 raise
1684 if data == b'':
1685 # BSD-style EOF
1686 break
1650 if output_filter:
1687 if output_filter:
1651 data = output_filter(data)
1688 data = output_filter(data)
1652 if self.logfile is not None:
1689 if self.logfile is not None:
@@ -1695,7 +1732,7 b' class spawn(object):'
1695 ##############################################################################
1732 ##############################################################################
1696 # The following methods are no longer supported or allowed.
1733 # The following methods are no longer supported or allowed.
1697
1734
1698 def setmaxread(self, maxread):
1735 def setmaxread(self, maxread): # pragma: no cover
1699
1736
1700 '''This method is no longer supported or allowed. I don't like getters
1737 '''This method is no longer supported or allowed. I don't like getters
1701 and setters without a good reason. '''
1738 and setters without a good reason. '''
@@ -1704,7 +1741,7 b' class spawn(object):'
1704 'or allowed. Just assign a value to the ' +
1741 'or allowed. Just assign a value to the ' +
1705 'maxread member variable.')
1742 'maxread member variable.')
1706
1743
1707 def setlog(self, fileobject):
1744 def setlog(self, fileobject): # pragma: no cover
1708
1745
1709 '''This method is no longer supported or allowed.
1746 '''This method is no longer supported or allowed.
1710 '''
1747 '''
@@ -1732,11 +1769,13 b' class spawnu(spawn):'
1732 allowed_string_types = (str, )
1769 allowed_string_types = (str, )
1733 _chr = staticmethod(chr)
1770 _chr = staticmethod(chr)
1734 linesep = os.linesep
1771 linesep = os.linesep
1772 crlf = '\r\n'
1735 else:
1773 else:
1736 string_type = unicode
1774 string_type = unicode
1737 allowed_string_types = (unicode, )
1775 allowed_string_types = (unicode, )
1738 _chr = staticmethod(unichr)
1776 _chr = staticmethod(unichr)
1739 linesep = os.linesep.decode('ascii')
1777 linesep = os.linesep.decode('ascii')
1778 crlf = '\r\n'.decode('ascii')
1740 # This can handle unicode in both Python 2 and 3
1779 # This can handle unicode in both Python 2 and 3
1741 write_to_stdout = sys.stdout.write
1780 write_to_stdout = sys.stdout.write
1742
1781
@@ -1959,16 +1998,56 b' class searcher_re(object):'
1959 return best_index
1998 return best_index
1960
1999
1961
2000
1962 def which(filename):
2001 def is_executable_file(path):
2002 """Checks that path is an executable regular file (or a symlink to a file).
2003
2004 This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``, but
2005 on some platforms :func:`os.access` gives us the wrong answer, so this
2006 checks permission bits directly.
2007 """
2008 # follow symlinks,
2009 fpath = os.path.realpath(path)
1963
2010
2011 # return False for non-files (directories, fifo, etc.)
2012 if not os.path.isfile(fpath):
2013 return False
2014
2015 # On Solaris, etc., "If the process has appropriate privileges, an
2016 # implementation may indicate success for X_OK even if none of the
2017 # execute file permission bits are set."
2018 #
2019 # For this reason, it is necessary to explicitly check st_mode
2020
2021 # get file mode using os.stat, and check if `other',
2022 # that is anybody, may read and execute.
2023 mode = os.stat(fpath).st_mode
2024 if mode & stat.S_IROTH and mode & stat.S_IXOTH:
2025 return True
2026
2027 # get current user's group ids, and check if `group',
2028 # when matching ours, may read and execute.
2029 user_gids = os.getgroups() + [os.getgid()]
2030 if (os.stat(fpath).st_gid in user_gids and
2031 mode & stat.S_IRGRP and mode & stat.S_IXGRP):
2032 return True
2033
2034 # finally, if file owner matches our effective userid,
2035 # check if `user', may read and execute.
2036 user_gids = os.getgroups() + [os.getgid()]
2037 if (os.stat(fpath).st_uid == os.geteuid() and
2038 mode & stat.S_IRUSR and mode & stat.S_IXUSR):
2039 return True
2040
2041 return False
2042
2043 def which(filename):
1964 '''This takes a given filename; tries to find it in the environment path;
2044 '''This takes a given filename; tries to find it in the environment path;
1965 then checks if it is executable. This returns the full path to the filename
2045 then checks if it is executable. This returns the full path to the filename
1966 if found and executable. Otherwise this returns None.'''
2046 if found and executable. Otherwise this returns None.'''
1967
2047
1968 # Special case where filename contains an explicit path.
2048 # Special case where filename contains an explicit path.
1969 if os.path.dirname(filename) != '':
2049 if os.path.dirname(filename) != '' and is_executable_file(filename):
1970 if os.access(filename, os.X_OK):
2050 return filename
1971 return filename
1972 if 'PATH' not in os.environ or os.environ['PATH'] == '':
2051 if 'PATH' not in os.environ or os.environ['PATH'] == '':
1973 p = os.defpath
2052 p = os.defpath
1974 else:
2053 else:
@@ -1976,7 +2055,7 b' def which(filename):'
1976 pathlist = p.split(os.pathsep)
2055 pathlist = p.split(os.pathsep)
1977 for path in pathlist:
2056 for path in pathlist:
1978 ff = os.path.join(path, filename)
2057 ff = os.path.join(path, filename)
1979 if os.access(ff, os.X_OK):
2058 if is_executable_file(ff):
1980 return ff
2059 return ff
1981 return None
2060 return None
1982
2061
@@ -2041,4 +2120,4 b' def split_command_line(command_line):'
2041 arg_list.append(arg)
2120 arg_list.append(arg)
2042 return arg_list
2121 return arg_list
2043
2122
2044 # vi:set sr et ts=4 sw=4 ft=python :
2123 # vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent :
@@ -13,19 +13,18 b' Developers of the IPython Notebook will need to install the following tools:'
13
13
14 We are moving to a model where our JavaScript dependencies are managed using
14 We are moving to a model where our JavaScript dependencies are managed using
15 [bower](http://bower.io/). These packages are installed in `static/components`
15 [bower](http://bower.io/). These packages are installed in `static/components`
16 and committed into our git repo. Our dependencies are described in the file
16 and committed into a separate git repo [ipython/ipython-components](ipython/ipython-components).
17 Our dependencies are described in the file
17 `static/components/bower.json`. To update our bower packages, run `fab update`
18 `static/components/bower.json`. To update our bower packages, run `fab update`
18 in this directory.
19 in this directory.
19
20
20 Because CodeMirror does not use proper semantic versioning for its GitHub tags,
21 we maintain our own fork of CodeMirror that is used with bower. This fork should
22 track the upstream CodeMirror exactly; the only difference is that we are adding
23 semantic versioned tags to our repo.
24
25 ## less
21 ## less
26
22
27 If you edit our `.less` files you will need to run the less compiler to build
23 If you edit our `.less` files you will need to run the less compiler to build
28 our minified css files. This can be done by running `fab css` from this directory.
24 our minified css files. This can be done by running `fab css` from this directory,
25 or `python setup.py css` from the root of the repository.
26 If you are working frequently with `.less` files please consider installing git hooks that
27 rebuild the css files and corresponding maps in `${RepoRoot}/git-hooks/install-hooks.sh`.
29
28
30 ## JavaScript Documentation
29 ## JavaScript Documentation
31
30
@@ -1,21 +1,7 b''
1 """Base Tornado handlers for the notebook.
1 """Base Tornado handlers for the notebook server."""
2
3 Authors:
4
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
19
5
20 import functools
6 import functools
21 import json
7 import json
@@ -41,7 +27,7 b' except ImportError:'
41 from IPython.config import Application
27 from IPython.config import Application
42 from IPython.utils.path import filefind
28 from IPython.utils.path import filefind
43 from IPython.utils.py3compat import string_types
29 from IPython.utils.py3compat import string_types
44 from IPython.html.utils import is_hidden
30 from IPython.html.utils import is_hidden, url_path_join, url_escape
45
31
46 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
47 # Top-level handlers
33 # Top-level handlers
@@ -53,6 +39,10 b' class AuthenticatedHandler(web.RequestHandler):'
53
39
54 def set_default_headers(self):
40 def set_default_headers(self):
55 headers = self.settings.get('headers', {})
41 headers = self.settings.get('headers', {})
42
43 if "X-Frame-Options" not in headers:
44 headers["X-Frame-Options"] = "SAMEORIGIN"
45
56 for header_name,value in headers.items() :
46 for header_name,value in headers.items() :
57 try:
47 try:
58 self.set_header(header_name, value)
48 self.set_header(header_name, value)
@@ -137,6 +127,10 b' class IPythonHandler(AuthenticatedHandler):'
137 @property
127 @property
138 def base_url(self):
128 def base_url(self):
139 return self.settings.get('base_url', '/')
129 return self.settings.get('base_url', '/')
130
131 @property
132 def ws_url(self):
133 return self.settings.get('websocket_url', '')
140
134
141 #---------------------------------------------------------------
135 #---------------------------------------------------------------
142 # Manager objects
136 # Manager objects
@@ -147,8 +141,8 b' class IPythonHandler(AuthenticatedHandler):'
147 return self.settings['kernel_manager']
141 return self.settings['kernel_manager']
148
142
149 @property
143 @property
150 def notebook_manager(self):
144 def contents_manager(self):
151 return self.settings['notebook_manager']
145 return self.settings['contents_manager']
152
146
153 @property
147 @property
154 def cluster_manager(self):
148 def cluster_manager(self):
@@ -162,9 +156,47 b' class IPythonHandler(AuthenticatedHandler):'
162 def kernel_spec_manager(self):
156 def kernel_spec_manager(self):
163 return self.settings['kernel_spec_manager']
157 return self.settings['kernel_spec_manager']
164
158
159 #---------------------------------------------------------------
160 # CORS
161 #---------------------------------------------------------------
162
165 @property
163 @property
166 def project_dir(self):
164 def allow_origin(self):
167 return self.notebook_manager.notebook_dir
165 """Normal Access-Control-Allow-Origin"""
166 return self.settings.get('allow_origin', '')
167
168 @property
169 def allow_origin_pat(self):
170 """Regular expression version of allow_origin"""
171 return self.settings.get('allow_origin_pat', None)
172
173 @property
174 def allow_credentials(self):
175 """Whether to set Access-Control-Allow-Credentials"""
176 return self.settings.get('allow_credentials', False)
177
178 def set_default_headers(self):
179 """Add CORS headers, if defined"""
180 super(IPythonHandler, self).set_default_headers()
181 if self.allow_origin:
182 self.set_header("Access-Control-Allow-Origin", self.allow_origin)
183 elif self.allow_origin_pat:
184 origin = self.get_origin()
185 if origin and self.allow_origin_pat.match(origin):
186 self.set_header("Access-Control-Allow-Origin", origin)
187 if self.allow_credentials:
188 self.set_header("Access-Control-Allow-Credentials", 'true')
189
190 def get_origin(self):
191 # Handle WebSocket Origin naming convention differences
192 # The difference between version 8 and 13 is that in 8 the
193 # client sends a "Sec-Websocket-Origin" header and in 13 it's
194 # simply "Origin".
195 if "Origin" in self.request.headers:
196 origin = self.request.headers.get("Origin")
197 else:
198 origin = self.request.headers.get("Sec-Websocket-Origin", None)
199 return origin
168
200
169 #---------------------------------------------------------------
201 #---------------------------------------------------------------
170 # template rendering
202 # template rendering
@@ -183,6 +215,7 b' class IPythonHandler(AuthenticatedHandler):'
183 def template_namespace(self):
215 def template_namespace(self):
184 return dict(
216 return dict(
185 base_url=self.base_url,
217 base_url=self.base_url,
218 ws_url=self.ws_url,
186 logged_in=self.logged_in,
219 logged_in=self.logged_in,
187 login_available=self.login_available,
220 login_available=self.login_available,
188 static_url=self.static_url,
221 static_url=self.static_url,
@@ -372,6 +405,37 b' class TrailingSlashHandler(web.RequestHandler):'
372 def get(self):
405 def get(self):
373 self.redirect(self.request.uri.rstrip('/'))
406 self.redirect(self.request.uri.rstrip('/'))
374
407
408
409 class FilesRedirectHandler(IPythonHandler):
410 """Handler for redirecting relative URLs to the /files/ handler"""
411 def get(self, path=''):
412 cm = self.contents_manager
413 if cm.path_exists(path):
414 # it's a *directory*, redirect to /tree
415 url = url_path_join(self.base_url, 'tree', path)
416 else:
417 orig_path = path
418 # otherwise, redirect to /files
419 parts = path.split('/')
420 path = '/'.join(parts[:-1])
421 name = parts[-1]
422
423 if not cm.file_exists(name=name, path=path) and 'files' in parts:
424 # redirect without files/ iff it would 404
425 # this preserves pre-2.0-style 'files/' links
426 self.log.warn("Deprecated files/ URL: %s", orig_path)
427 parts.remove('files')
428 path = '/'.join(parts[:-1])
429
430 if not cm.file_exists(name=name, path=path):
431 raise web.HTTPError(404)
432
433 url = url_path_join(self.base_url, 'files', path, name)
434 url = url_escape(url)
435 self.log.debug("Redirecting %s to %s", self.request.path, url)
436 self.redirect(url)
437
438
375 #-----------------------------------------------------------------------------
439 #-----------------------------------------------------------------------------
376 # URL pattern fragments for re-use
440 # URL pattern fragments for re-use
377 #-----------------------------------------------------------------------------
441 #-----------------------------------------------------------------------------
@@ -379,6 +443,8 b' class TrailingSlashHandler(web.RequestHandler):'
379 path_regex = r"(?P<path>(?:/.*)*)"
443 path_regex = r"(?P<path>(?:/.*)*)"
380 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
444 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
381 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
445 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
446 file_name_regex = r"(?P<name>[^/]+)"
447 file_path_regex = "%s/%s" % (path_regex, file_name_regex)
382
448
383 #-----------------------------------------------------------------------------
449 #-----------------------------------------------------------------------------
384 # URL to handler mappings
450 # URL to handler mappings
@@ -15,6 +15,9 b' try:'
15 except ImportError:
15 except ImportError:
16 from Cookie import SimpleCookie # Py 2
16 from Cookie import SimpleCookie # Py 2
17 import logging
17 import logging
18
19 import tornado
20 from tornado import ioloop
18 from tornado import web
21 from tornado import web
19 from tornado import websocket
22 from tornado import websocket
20
23
@@ -26,29 +29,36 b' from .handlers import IPythonHandler'
26
29
27
30
28 class ZMQStreamHandler(websocket.WebSocketHandler):
31 class ZMQStreamHandler(websocket.WebSocketHandler):
29
32
30 def same_origin(self):
33 def check_origin(self, origin):
31 """Check to see that origin and host match in the headers."""
34 """Check Origin == Host or Access-Control-Allow-Origin.
32
35
33 # The difference between version 8 and 13 is that in 8 the
36 Tornado >= 4 calls this method automatically, raising 403 if it returns False.
34 # client sends a "Sec-Websocket-Origin" header and in 13 it's
37 We call it explicitly in `open` on Tornado < 4.
35 # simply "Origin".
38 """
36 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"):
39 if self.allow_origin == '*':
37 origin_header = self.request.headers.get("Sec-Websocket-Origin")
40 return True
38 else:
39 origin_header = self.request.headers.get("Origin")
40
41
41 host = self.request.headers.get("Host")
42 host = self.request.headers.get("Host")
42
43
43 # If no header is provided, assume we can't verify origin
44 # If no header is provided, assume we can't verify origin
44 if(origin_header is None or host is None):
45 if(origin is None or host is None):
46 return False
47
48 host_origin = "{0}://{1}".format(self.request.protocol, host)
49
50 # OK if origin matches host
51 if origin == host_origin:
52 return True
53
54 # Check CORS headers
55 if self.allow_origin:
56 return self.allow_origin == origin
57 elif self.allow_origin_pat:
58 return bool(self.allow_origin_pat.match(origin))
59 else:
60 # No CORS headers deny the request
45 return False
61 return False
46
47 parsed_origin = urlparse(origin_header)
48 origin = parsed_origin.netloc
49
50 # Check to see that origin matches host directly, including ports
51 return origin == host
52
62
53 def clear_cookie(self, *args, **kwargs):
63 def clear_cookie(self, *args, **kwargs):
54 """meaningless for websockets"""
64 """meaningless for websockets"""
@@ -94,19 +104,41 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
94 """
104 """
95 return True
105 return True
96
106
107 # ping interval for keeping websockets alive (30 seconds)
108 WS_PING_INTERVAL = 30000
97
109
98 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
110 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
111 ping_callback = None
112
113 def set_default_headers(self):
114 """Undo the set_default_headers in IPythonHandler
115
116 which doesn't make sense for websockets
117 """
118 pass
99
119
100 def open(self, kernel_id):
120 def open(self, kernel_id):
101 self.kernel_id = cast_unicode(kernel_id, 'ascii')
121 self.kernel_id = cast_unicode(kernel_id, 'ascii')
102 # Check to see that origin matches host directly, including ports
122 # Check to see that origin matches host directly, including ports
103 if not self.same_origin():
123 # Tornado 4 already does CORS checking
104 self.log.warn("Cross Origin WebSocket Attempt.")
124 if tornado.version_info[0] < 4:
105 raise web.HTTPError(404)
125 if not self.check_origin(self.get_origin()):
126 self.log.warn("Cross Origin WebSocket Attempt from %s", self.get_origin())
127 raise web.HTTPError(403)
106
128
107 self.session = Session(config=self.config)
129 self.session = Session(config=self.config)
108 self.save_on_message = self.on_message
130 self.save_on_message = self.on_message
109 self.on_message = self.on_first_message
131 self.on_message = self.on_first_message
132 self.ping_callback = ioloop.PeriodicCallback(self.send_ping, WS_PING_INTERVAL)
133 self.ping_callback.start()
134
135 def send_ping(self):
136 """send a ping to keep the websocket alive"""
137 if self.stream.closed() and self.ping_callback is not None:
138 self.ping_callback.stop()
139 return
140
141 self.ping(b'')
110
142
111 def _inject_cookie_message(self, msg):
143 def _inject_cookie_message(self, msg):
112 """Inject the first message, which is the document cookie,
144 """Inject the first message, which is the document cookie,
@@ -8,31 +8,65 b' from subprocess import check_output'
8
8
9 pjoin = os.path.join
9 pjoin = os.path.join
10 static_dir = 'static'
10 static_dir = 'static'
11 components_dir = os.path.join(static_dir, 'components')
11 components_dir = pjoin(static_dir, 'components')
12 here = os.path.dirname(__file__)
12
13
13 min_less_version = '1.4.0'
14 min_less_version = '1.7.0'
14 max_less_version = '1.5.0' # exclusive
15 max_less_version = '1.8.0' # exclusive
15
16
16 def css(minify=True, verbose=False):
17 def _need_css_update():
18 """Does less need to run?"""
19
20 static_path = pjoin(here, static_dir)
21 css_targets = [
22 pjoin(static_path, 'style', '%s.min.css' % name)
23 for name in ('style', 'ipython')
24 ]
25 css_maps = [t + '.map' for t in css_targets]
26 targets = css_targets + css_maps
27 if not all(os.path.exists(t) for t in targets):
28 # some generated files don't exist
29 return True
30 earliest_target = sorted(os.stat(t).st_mtime for t in targets)[0]
31
32 # check if any .less files are newer than the generated targets
33 for (dirpath, dirnames, filenames) in os.walk(static_path):
34 for f in filenames:
35 if f.endswith('.less'):
36 path = pjoin(static_path, dirpath, f)
37 timestamp = os.stat(path).st_mtime
38 if timestamp > earliest_target:
39 return True
40
41 return False
42
43 def css(minify=False, verbose=False, force=False):
17 """generate the css from less files"""
44 """generate the css from less files"""
45 minify = _to_bool(minify)
46 verbose = _to_bool(verbose)
47 force = _to_bool(force)
48 # minify implies force because it's not the default behavior
49 if not force and not minify and not _need_css_update():
50 print("css up-to-date")
51 return
52
18 for name in ('style', 'ipython'):
53 for name in ('style', 'ipython'):
19 source = pjoin('style', "%s.less" % name)
54 source = pjoin('style', "%s.less" % name)
20 target = pjoin('style', "%s.min.css" % name)
55 target = pjoin('style', "%s.min.css" % name)
21 _compile_less(source, target, minify, verbose)
56 sourcemap = pjoin('style', "%s.min.css.map" % name)
57 _compile_less(source, target, sourcemap, minify, verbose)
22
58
23 def _to_bool(b):
59 def _to_bool(b):
24 if not b in ['True', 'False', True, False]:
60 if not b in ['True', 'False', True, False]:
25 abort('boolean expected, got: %s' % b)
61 abort('boolean expected, got: %s' % b)
26 return (b in ['True', True])
62 return (b in ['True', True])
27
63
28 def _compile_less(source, target, minify=True, verbose=False):
64 def _compile_less(source, target, sourcemap, minify=True, verbose=False):
29 """Compile a less file by source and target relative to static_dir"""
65 """Compile a less file by source and target relative to static_dir"""
30 minify = _to_bool(minify)
31 verbose = _to_bool(verbose)
32 min_flag = '-x' if minify is True else ''
66 min_flag = '-x' if minify is True else ''
33 ver_flag = '--verbose' if verbose is True else ''
67 ver_flag = '--verbose' if verbose is True else ''
34
68
35 # pin less to 1.4
69 # pin less to version number from above
36 try:
70 try:
37 out = check_output(['lessc', '--version'])
71 out = check_output(['lessc', '--version'])
38 except OSError as err:
72 except OSError as err:
@@ -45,6 +79,7 b' def _compile_less(source, target, minify=True, verbose=False):'
45 if V(less_version) >= V(max_less_version):
79 if V(less_version) >= V(max_less_version):
46 raise ValueError("lessc too new: %s >= %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, max_less_version))
80 raise ValueError("lessc too new: %s >= %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, max_less_version))
47
81
82 static_path = pjoin(here, static_dir)
48 with lcd(static_dir):
83 with lcd(static_dir):
49 local('lessc {min_flag} {ver_flag} {source} {target}'.format(**locals()))
84 local('lessc {min_flag} {ver_flag} --source-map={sourcemap} --source-map-basepath={static_path} --source-map-rootpath="../" {source} {target}'.format(**locals()))
50
85
@@ -1,10 +1,18 b''
1 """Tornado handlers for nbconvert."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
1 import io
6 import io
2 import os
7 import os
3 import zipfile
8 import zipfile
4
9
5 from tornado import web
10 from tornado import web
6
11
7 from ..base.handlers import IPythonHandler, notebook_path_regex
12 from ..base.handlers import (
13 IPythonHandler, FilesRedirectHandler,
14 notebook_path_regex, path_regex,
15 )
8 from IPython.nbformat.current import to_notebook_json
16 from IPython.nbformat.current import to_notebook_json
9
17
10 from IPython.utils.py3compat import cast_bytes
18 from IPython.utils.py3compat import cast_bytes
@@ -73,7 +81,7 b' class NbconvertFileHandler(IPythonHandler):'
73 exporter = get_exporter(format, config=self.config, log=self.log)
81 exporter = get_exporter(format, config=self.config, log=self.log)
74
82
75 path = path.strip('/')
83 path = path.strip('/')
76 model = self.notebook_manager.get_notebook(name=name, path=path)
84 model = self.contents_manager.get_model(name=name, path=path)
77
85
78 self.set_header('Last-Modified', model['last_modified'])
86 self.set_header('Last-Modified', model['last_modified'])
79
87
@@ -123,6 +131,7 b' class NbconvertPostHandler(IPythonHandler):'
123
131
124 self.finish(output)
132 self.finish(output)
125
133
134
126 #-----------------------------------------------------------------------------
135 #-----------------------------------------------------------------------------
127 # URL to handler mappings
136 # URL to handler mappings
128 #-----------------------------------------------------------------------------
137 #-----------------------------------------------------------------------------
@@ -134,4 +143,5 b' default_handlers = ['
134 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
143 (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
135 NbconvertFileHandler),
144 NbconvertFileHandler),
136 (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
145 (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
146 (r"/nbconvert/html%s" % path_regex, FilesRedirectHandler),
137 ]
147 ]
@@ -106,7 +106,7 b' class APITest(NotebookTestBase):'
106
106
107 @onlyif_cmds_exist('pandoc')
107 @onlyif_cmds_exist('pandoc')
108 def test_from_post(self):
108 def test_from_post(self):
109 nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
109 nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
110 nbmodel = requests.get(nbmodel_url).json()
110 nbmodel = requests.get(nbmodel_url).json()
111
111
112 r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
112 r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
@@ -121,7 +121,7 b' class APITest(NotebookTestBase):'
121
121
122 @onlyif_cmds_exist('pandoc')
122 @onlyif_cmds_exist('pandoc')
123 def test_from_post_zip(self):
123 def test_from_post_zip(self):
124 nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
124 nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
125 nbmodel = requests.get(nbmodel_url).json()
125 nbmodel = requests.get(nbmodel_url).json()
126
126
127 r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel)
127 r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel)
@@ -1,31 +1,17 b''
1 """Tornado handlers for the live notebook view.
1 """Tornado handlers for the live notebook view."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 import os
6 import os
20 from tornado import web
7 from tornado import web
21 HTTPError = web.HTTPError
8 HTTPError = web.HTTPError
22
9
23 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
10 from ..base.handlers import (
24 from ..utils import url_path_join, url_escape
11 IPythonHandler, FilesRedirectHandler,
25
12 notebook_path_regex, path_regex,
26 #-----------------------------------------------------------------------------
13 )
27 # Handlers
14 from ..utils import url_escape
28 #-----------------------------------------------------------------------------
29
15
30
16
31 class NotebookHandler(IPythonHandler):
17 class NotebookHandler(IPythonHandler):
@@ -35,17 +21,16 b' class NotebookHandler(IPythonHandler):'
35 """get renders the notebook template if a name is given, or
21 """get renders the notebook template if a name is given, or
36 redirects to the '/files/' handler if the name is not given."""
22 redirects to the '/files/' handler if the name is not given."""
37 path = path.strip('/')
23 path = path.strip('/')
38 nbm = self.notebook_manager
24 cm = self.contents_manager
39 if name is None:
25 if name is None:
40 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
26 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
41
27
42 # a .ipynb filename was given
28 # a .ipynb filename was given
43 if not nbm.notebook_exists(name, path):
29 if not cm.file_exists(name, path):
44 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
30 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
45 name = url_escape(name)
31 name = url_escape(name)
46 path = url_escape(path)
32 path = url_escape(path)
47 self.write(self.render_template('notebook.html',
33 self.write(self.render_template('notebook.html',
48 project=self.project_dir,
49 notebook_path=path,
34 notebook_path=path,
50 notebook_name=name,
35 notebook_name=name,
51 kill_kernel=False,
36 kill_kernel=False,
@@ -53,30 +38,6 b' class NotebookHandler(IPythonHandler):'
53 )
38 )
54 )
39 )
55
40
56 class NotebookRedirectHandler(IPythonHandler):
57 def get(self, path=''):
58 nbm = self.notebook_manager
59 if nbm.path_exists(path):
60 # it's a *directory*, redirect to /tree
61 url = url_path_join(self.base_url, 'tree', path)
62 else:
63 # otherwise, redirect to /files
64 if '/files/' in path:
65 # redirect without files/ iff it would 404
66 # this preserves pre-2.0-style 'files/' links
67 # FIXME: this is hardcoded based on notebook_path,
68 # but so is the files handler itself,
69 # so it should work until both are cleaned up.
70 parts = path.split('/')
71 files_path = os.path.join(nbm.notebook_dir, *parts)
72 if not os.path.exists(files_path):
73 self.log.warn("Deprecated files/ URL: %s", path)
74 path = path.replace('/files/', '/', 1)
75
76 url = url_path_join(self.base_url, 'files', path)
77 url = url_escape(url)
78 self.log.debug("Redirecting %s to %s", self.request.path, url)
79 self.redirect(url)
80
41
81 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
82 # URL to handler mappings
43 # URL to handler mappings
@@ -85,6 +46,6 b' class NotebookRedirectHandler(IPythonHandler):'
85
46
86 default_handlers = [
47 default_handlers = [
87 (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
48 (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
88 (r"/notebooks%s" % path_regex, NotebookRedirectHandler),
49 (r"/notebooks%s" % path_regex, FilesRedirectHandler),
89 ]
50 ]
90
51
@@ -6,12 +6,14 b''
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import errno
10 import errno
10 import io
11 import io
11 import json
12 import json
12 import logging
13 import logging
13 import os
14 import os
14 import random
15 import random
16 import re
15 import select
17 import select
16 import signal
18 import signal
17 import socket
19 import socket
@@ -53,8 +55,8 b' from IPython.html import DEFAULT_STATIC_FILES_PATH'
53 from .base.handlers import Template404
55 from .base.handlers import Template404
54 from .log import log_request
56 from .log import log_request
55 from .services.kernels.kernelmanager import MappingKernelManager
57 from .services.kernels.kernelmanager import MappingKernelManager
56 from .services.notebooks.nbmanager import NotebookManager
58 from .services.contents.manager import ContentsManager
57 from .services.notebooks.filenbmanager import FileNotebookManager
59 from .services.contents.filemanager import FileContentsManager
58 from .services.clusters.clustermanager import ClusterManager
60 from .services.clusters.clustermanager import ClusterManager
59 from .services.sessions.sessionmanager import SessionManager
61 from .services.sessions.sessionmanager import SessionManager
60
62
@@ -72,6 +74,7 b' from IPython.kernel.zmq.session import default_secure, Session'
72 from IPython.nbformat.sign import NotebookNotary
74 from IPython.nbformat.sign import NotebookNotary
73 from IPython.utils.importstring import import_item
75 from IPython.utils.importstring import import_item
74 from IPython.utils import submodule
76 from IPython.utils import submodule
77 from IPython.utils.process import check_pid
75 from IPython.utils.traitlets import (
78 from IPython.utils.traitlets import (
76 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
79 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
77 DottedObjectName, TraitError,
80 DottedObjectName, TraitError,
@@ -118,19 +121,19 b' def load_handlers(name):'
118
121
119 class NotebookWebApplication(web.Application):
122 class NotebookWebApplication(web.Application):
120
123
121 def __init__(self, ipython_app, kernel_manager, notebook_manager,
124 def __init__(self, ipython_app, kernel_manager, contents_manager,
122 cluster_manager, session_manager, kernel_spec_manager, log,
125 cluster_manager, session_manager, kernel_spec_manager, log,
123 base_url, settings_overrides, jinja_env_options):
126 base_url, settings_overrides, jinja_env_options):
124
127
125 settings = self.init_settings(
128 settings = self.init_settings(
126 ipython_app, kernel_manager, notebook_manager, cluster_manager,
129 ipython_app, kernel_manager, contents_manager, cluster_manager,
127 session_manager, kernel_spec_manager, log, base_url,
130 session_manager, kernel_spec_manager, log, base_url,
128 settings_overrides, jinja_env_options)
131 settings_overrides, jinja_env_options)
129 handlers = self.init_handlers(settings)
132 handlers = self.init_handlers(settings)
130
133
131 super(NotebookWebApplication, self).__init__(handlers, **settings)
134 super(NotebookWebApplication, self).__init__(handlers, **settings)
132
135
133 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
136 def init_settings(self, ipython_app, kernel_manager, contents_manager,
134 cluster_manager, session_manager, kernel_spec_manager,
137 cluster_manager, session_manager, kernel_spec_manager,
135 log, base_url, settings_overrides,
138 log, base_url, settings_overrides,
136 jinja_env_options=None):
139 jinja_env_options=None):
@@ -162,13 +165,14 b' class NotebookWebApplication(web.Application):'
162
165
163 # managers
166 # managers
164 kernel_manager=kernel_manager,
167 kernel_manager=kernel_manager,
165 notebook_manager=notebook_manager,
168 contents_manager=contents_manager,
166 cluster_manager=cluster_manager,
169 cluster_manager=cluster_manager,
167 session_manager=session_manager,
170 session_manager=session_manager,
168 kernel_spec_manager=kernel_spec_manager,
171 kernel_spec_manager=kernel_spec_manager,
169
172
170 # IPython stuff
173 # IPython stuff
171 nbextensions_path = ipython_app.nbextensions_path,
174 nbextensions_path = ipython_app.nbextensions_path,
175 websocket_url=ipython_app.websocket_url,
172 mathjax_url=ipython_app.mathjax_url,
176 mathjax_url=ipython_app.mathjax_url,
173 config=ipython_app.config,
177 config=ipython_app.config,
174 jinja2_env=env,
178 jinja2_env=env,
@@ -189,18 +193,20 b' class NotebookWebApplication(web.Application):'
189 handlers.extend(load_handlers('nbconvert.handlers'))
193 handlers.extend(load_handlers('nbconvert.handlers'))
190 handlers.extend(load_handlers('kernelspecs.handlers'))
194 handlers.extend(load_handlers('kernelspecs.handlers'))
191 handlers.extend(load_handlers('services.kernels.handlers'))
195 handlers.extend(load_handlers('services.kernels.handlers'))
192 handlers.extend(load_handlers('services.notebooks.handlers'))
196 handlers.extend(load_handlers('services.contents.handlers'))
193 handlers.extend(load_handlers('services.clusters.handlers'))
197 handlers.extend(load_handlers('services.clusters.handlers'))
194 handlers.extend(load_handlers('services.sessions.handlers'))
198 handlers.extend(load_handlers('services.sessions.handlers'))
195 handlers.extend(load_handlers('services.nbconvert.handlers'))
199 handlers.extend(load_handlers('services.nbconvert.handlers'))
196 handlers.extend(load_handlers('services.kernelspecs.handlers'))
200 handlers.extend(load_handlers('services.kernelspecs.handlers'))
197 # FIXME: /files/ should be handled by the Contents service when it exists
201 # FIXME: /files/ should be handled by the Contents service when it exists
198 nbm = settings['notebook_manager']
202 cm = settings['contents_manager']
199 if hasattr(nbm, 'notebook_dir'):
203 if hasattr(cm, 'root_dir'):
200 handlers.extend([
204 handlers.append(
201 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
205 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : cm.root_dir}),
206 )
207 handlers.append(
202 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
208 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
203 ])
209 )
204 # prepend base_url onto the patterns that we match
210 # prepend base_url onto the patterns that we match
205 new_handlers = []
211 new_handlers = []
206 for handler in handlers:
212 for handler in handlers:
@@ -260,9 +266,9 b" flags['no-mathjax']=("
260 )
266 )
261
267
262 # Add notebook manager flags
268 # Add notebook manager flags
263 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
269 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
264 'Auto-save a .py script everytime the .ipynb notebook is saved',
270 'DEPRECATED, IGNORED',
265 'Do not auto-save .py scripts for every notebook'))
271 'DEPRECATED, IGNORED'))
266
272
267 aliases = dict(base_aliases)
273 aliases = dict(base_aliases)
268
274
@@ -298,7 +304,7 b' class NotebookApp(BaseIPythonApplication):'
298
304
299 classes = [
305 classes = [
300 KernelManager, ProfileDir, Session, MappingKernelManager,
306 KernelManager, ProfileDir, Session, MappingKernelManager,
301 NotebookManager, FileNotebookManager, NotebookNotary,
307 ContentsManager, FileContentsManager, NotebookNotary,
302 ]
308 ]
303 flags = Dict(flags)
309 flags = Dict(flags)
304 aliases = Dict(aliases)
310 aliases = Dict(aliases)
@@ -333,8 +339,34 b' class NotebookApp(BaseIPythonApplication):'
333 self.file_to_run = base
339 self.file_to_run = base
334 self.notebook_dir = path
340 self.notebook_dir = path
335
341
336 # Network related information.
342 # Network related information
337
343
344 allow_origin = Unicode('', config=True,
345 help="""Set the Access-Control-Allow-Origin header
346
347 Use '*' to allow any origin to access your server.
348
349 Takes precedence over allow_origin_pat.
350 """
351 )
352
353 allow_origin_pat = Unicode('', config=True,
354 help="""Use a regular expression for the Access-Control-Allow-Origin header
355
356 Requests from an origin matching the expression will get replies with:
357
358 Access-Control-Allow-Origin: origin
359
360 where `origin` is the origin of the request.
361
362 Ignored if allow_origin is set.
363 """
364 )
365
366 allow_credentials = Bool(False, config=True,
367 help="Set the Access-Control-Allow-Credentials: true header"
368 )
369
338 ip = Unicode('localhost', config=True,
370 ip = Unicode('localhost', config=True,
339 help="The IP address the notebook server will listen on."
371 help="The IP address the notebook server will listen on."
340 )
372 )
@@ -357,6 +389,14 b' class NotebookApp(BaseIPythonApplication):'
357 help="""The full path to a private key file for usage with SSL/TLS."""
389 help="""The full path to a private key file for usage with SSL/TLS."""
358 )
390 )
359
391
392 cookie_secret_file = Unicode(config=True,
393 help="""The file where the cookie secret is stored."""
394 )
395 def _cookie_secret_file_default(self):
396 if self.profile_dir is None:
397 return ''
398 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
399
360 cookie_secret = Bytes(b'', config=True,
400 cookie_secret = Bytes(b'', config=True,
361 help="""The random bytes used to secure cookies.
401 help="""The random bytes used to secure cookies.
362 By default this is a new random number every time you start the Notebook.
402 By default this is a new random number every time you start the Notebook.
@@ -367,7 +407,26 b' class NotebookApp(BaseIPythonApplication):'
367 """
407 """
368 )
408 )
369 def _cookie_secret_default(self):
409 def _cookie_secret_default(self):
370 return os.urandom(1024)
410 if os.path.exists(self.cookie_secret_file):
411 with io.open(self.cookie_secret_file, 'rb') as f:
412 return f.read()
413 else:
414 secret = base64.encodestring(os.urandom(1024))
415 self._write_cookie_secret_file(secret)
416 return secret
417
418 def _write_cookie_secret_file(self, secret):
419 """write my secret to my secret_file"""
420 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
421 with io.open(self.cookie_secret_file, 'wb') as f:
422 f.write(secret)
423 try:
424 os.chmod(self.cookie_secret_file, 0o600)
425 except OSError:
426 self.log.warn(
427 "Could not set permissions on %s",
428 self.cookie_secret_file
429 )
371
430
372 password = Unicode(u'', config=True,
431 password = Unicode(u'', config=True,
373 help="""Hashed password to use for web authentication.
432 help="""Hashed password to use for web authentication.
@@ -456,6 +515,13 b' class NotebookApp(BaseIPythonApplication):'
456 def _nbextensions_path_default(self):
515 def _nbextensions_path_default(self):
457 return [os.path.join(get_ipython_dir(), 'nbextensions')]
516 return [os.path.join(get_ipython_dir(), 'nbextensions')]
458
517
518 websocket_url = Unicode("", config=True,
519 help="""The base URL for websockets,
520 if it differs from the HTTP server (hint: it almost certainly doesn't).
521
522 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
523 """
524 )
459 mathjax_url = Unicode("", config=True,
525 mathjax_url = Unicode("", config=True,
460 help="""The url for MathJax.js."""
526 help="""The url for MathJax.js."""
461 )
527 )
@@ -482,13 +548,7 b' class NotebookApp(BaseIPythonApplication):'
482 return url
548 return url
483
549
484 # no local mathjax, serve from CDN
550 # no local mathjax, serve from CDN
485 if self.certfile:
551 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
486 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
487 host = u"https://c328740.ssl.cf1.rackcdn.com"
488 else:
489 host = u"http://cdn.mathjax.org"
490
491 url = host + u"/mathjax/latest/MathJax.js"
492 self.log.info("Using MathJax from CDN: %s", url)
552 self.log.info("Using MathJax from CDN: %s", url)
493 return url
553 return url
494
554
@@ -499,7 +559,7 b' class NotebookApp(BaseIPythonApplication):'
499 else:
559 else:
500 self.log.info("Using MathJax: %s", new)
560 self.log.info("Using MathJax: %s", new)
501
561
502 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
562 contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
503 config=True,
563 config=True,
504 help='The notebook manager class to use.'
564 help='The notebook manager class to use.'
505 )
565 )
@@ -563,7 +623,7 b' class NotebookApp(BaseIPythonApplication):'
563 raise TraitError("No such notebook dir: %r" % new)
623 raise TraitError("No such notebook dir: %r" % new)
564
624
565 # setting App.notebook_dir implies setting notebook and kernel dirs as well
625 # setting App.notebook_dir implies setting notebook and kernel dirs as well
566 self.config.FileNotebookManager.notebook_dir = new
626 self.config.FileContentsManager.root_dir = new
567 self.config.MappingKernelManager.root_dir = new
627 self.config.MappingKernelManager.root_dir = new
568
628
569
629
@@ -589,11 +649,8 b' class NotebookApp(BaseIPythonApplication):'
589
649
590 def init_kernel_argv(self):
650 def init_kernel_argv(self):
591 """construct the kernel arguments"""
651 """construct the kernel arguments"""
592 self.kernel_argv = []
593 # Kernel should inherit default config file from frontend
594 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
595 # Kernel should get *absolute* path to profile directory
652 # Kernel should get *absolute* path to profile directory
596 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
653 self.kernel_argv = ["--profile-dir", self.profile_dir.location]
597
654
598 def init_configurables(self):
655 def init_configurables(self):
599 # force Session default to be secure
656 # force Session default to be secure
@@ -603,10 +660,12 b' class NotebookApp(BaseIPythonApplication):'
603 parent=self, log=self.log, kernel_argv=self.kernel_argv,
660 parent=self, log=self.log, kernel_argv=self.kernel_argv,
604 connection_dir = self.profile_dir.security_dir,
661 connection_dir = self.profile_dir.security_dir,
605 )
662 )
606 kls = import_item(self.notebook_manager_class)
663 kls = import_item(self.contents_manager_class)
607 self.notebook_manager = kls(parent=self, log=self.log)
664 self.contents_manager = kls(parent=self, log=self.log)
608 kls = import_item(self.session_manager_class)
665 kls = import_item(self.session_manager_class)
609 self.session_manager = kls(parent=self, log=self.log)
666 self.session_manager = kls(parent=self, log=self.log,
667 kernel_manager=self.kernel_manager,
668 contents_manager=self.contents_manager)
610 kls = import_item(self.cluster_manager_class)
669 kls = import_item(self.cluster_manager_class)
611 self.cluster_manager = kls(parent=self, log=self.log)
670 self.cluster_manager = kls(parent=self, log=self.log)
612 self.cluster_manager.update_profiles()
671 self.cluster_manager.update_profiles()
@@ -625,8 +684,13 b' class NotebookApp(BaseIPythonApplication):'
625
684
626 def init_webapp(self):
685 def init_webapp(self):
627 """initialize tornado webapp and httpserver"""
686 """initialize tornado webapp and httpserver"""
687 self.webapp_settings['allow_origin'] = self.allow_origin
688 if self.allow_origin_pat:
689 self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
690 self.webapp_settings['allow_credentials'] = self.allow_credentials
691
628 self.web_app = NotebookWebApplication(
692 self.web_app = NotebookWebApplication(
629 self, self.kernel_manager, self.notebook_manager,
693 self, self.kernel_manager, self.contents_manager,
630 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
694 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
631 self.log, self.base_url, self.webapp_settings,
695 self.log, self.base_url, self.webapp_settings,
632 self.jinja_environment_options
696 self.jinja_environment_options
@@ -717,8 +781,6 b' class NotebookApp(BaseIPythonApplication):'
717
781
718 This doesn't work on Windows.
782 This doesn't work on Windows.
719 """
783 """
720 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
721 time.sleep(0.1)
722 info = self.log.info
784 info = self.log.info
723 info('interrupted')
785 info('interrupted')
724 print(self.notebook_info())
786 print(self.notebook_info())
@@ -778,7 +840,7 b' class NotebookApp(BaseIPythonApplication):'
778
840
779 def notebook_info(self):
841 def notebook_info(self):
780 "Return the current working directory and the server url information"
842 "Return the current working directory and the server url information"
781 info = self.notebook_manager.info_string() + "\n"
843 info = self.contents_manager.info_string() + "\n"
782 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
844 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
783 return info + "The IPython Notebook is running at: %s" % self.display_url
845 return info + "The IPython Notebook is running at: %s" % self.display_url
784
846
@@ -790,6 +852,7 b' class NotebookApp(BaseIPythonApplication):'
790 'secure': bool(self.certfile),
852 'secure': bool(self.certfile),
791 'base_url': self.base_url,
853 'base_url': self.base_url,
792 'notebook_dir': os.path.abspath(self.notebook_dir),
854 'notebook_dir': os.path.abspath(self.notebook_dir),
855 'pid': os.getpid()
793 }
856 }
794
857
795 def write_server_info_file(self):
858 def write_server_info_file(self):
@@ -863,8 +926,17 b" def list_running_servers(profile='default'):"
863 for file in os.listdir(pd.security_dir):
926 for file in os.listdir(pd.security_dir):
864 if file.startswith('nbserver-'):
927 if file.startswith('nbserver-'):
865 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
928 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
866 yield json.load(f)
929 info = json.load(f)
867
930
931 # Simple check whether that process is really still running
932 if check_pid(info['pid']):
933 yield info
934 else:
935 # If the process has died, try to delete its info file
936 try:
937 os.unlink(file)
938 except OSError:
939 pass # TODO: This should warn or log or something
868 #-----------------------------------------------------------------------------
940 #-----------------------------------------------------------------------------
869 # Main entry point
941 # Main entry point
870 #-----------------------------------------------------------------------------
942 #-----------------------------------------------------------------------------
@@ -21,7 +21,6 b' from zmq.eventloop import ioloop'
21
21
22 from IPython.config.configurable import LoggingConfigurable
22 from IPython.config.configurable import LoggingConfigurable
23 from IPython.utils.traitlets import Dict, Instance, CFloat
23 from IPython.utils.traitlets import Dict, Instance, CFloat
24 from IPython.parallel.apps.ipclusterapp import IPClusterStart
25 from IPython.core.profileapp import list_profiles_in
24 from IPython.core.profileapp import list_profiles_in
26 from IPython.core.profiledir import ProfileDir
25 from IPython.core.profiledir import ProfileDir
27 from IPython.utils import py3compat
26 from IPython.utils import py3compat
@@ -33,17 +32,6 b' from IPython.utils.path import get_ipython_dir'
33 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
34
33
35
34
36 class DummyIPClusterStart(IPClusterStart):
37 """Dummy subclass to skip init steps that conflict with global app.
38
39 Instantiating and initializing this class should result in fully configured
40 launchers, but no other side effects or state.
41 """
42
43 def init_signal(self):
44 pass
45 def reinit_logging(self):
46 pass
47
35
48
36
49 class ClusterManager(LoggingConfigurable):
37 class ClusterManager(LoggingConfigurable):
@@ -59,6 +47,20 b' class ClusterManager(LoggingConfigurable):'
59 return IOLoop.instance()
47 return IOLoop.instance()
60
48
61 def build_launchers(self, profile_dir):
49 def build_launchers(self, profile_dir):
50 from IPython.parallel.apps.ipclusterapp import IPClusterStart
51
52 class DummyIPClusterStart(IPClusterStart):
53 """Dummy subclass to skip init steps that conflict with global app.
54
55 Instantiating and initializing this class should result in fully configured
56 launchers, but no other side effects or state.
57 """
58
59 def init_signal(self):
60 pass
61 def reinit_logging(self):
62 pass
63
62 starter = DummyIPClusterStart(log=self.log)
64 starter = DummyIPClusterStart(log=self.log)
63 starter.initialize(['--profile-dir', profile_dir])
65 starter.initialize(['--profile-dir', profile_dir])
64 cl = starter.controller_launcher
66 cl = starter.controller_launcher
1 NO CONTENT: file renamed from IPython/html/services/notebooks/__init__.py to IPython/html/services/contents/__init__.py
NO CONTENT: file renamed from IPython/html/services/notebooks/__init__.py to IPython/html/services/contents/__init__.py
1 NO CONTENT: file renamed from IPython/html/services/notebooks/tests/__init__.py to IPython/html/services/contents/tests/__init__.py
NO CONTENT: file renamed from IPython/html/services/notebooks/tests/__init__.py to IPython/html/services/contents/tests/__init__.py
@@ -1,6 +1,7 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Test the notebooks webservice API."""
2 """Test the contents webservice API."""
3
3
4 import base64
4 import io
5 import io
5 import json
6 import json
6 import os
7 import os
@@ -21,23 +22,21 b' from IPython.utils import py3compat'
21 from IPython.utils.data import uniq_stable
22 from IPython.utils.data import uniq_stable
22
23
23
24
24 # TODO: Remove this after we create the contents web service and directories are
25 def notebooks_only(dir_model):
25 # no longer listed by the notebook web service.
26 return [nb for nb in dir_model['content'] if nb['type']=='notebook']
26 def notebooks_only(nb_list):
27 return [nb for nb in nb_list if nb['type']=='notebook']
28
27
29 def dirs_only(nb_list):
28 def dirs_only(dir_model):
30 return [x for x in nb_list if x['type']=='directory']
29 return [x for x in dir_model['content'] if x['type']=='directory']
31
30
32
31
33 class NBAPI(object):
32 class API(object):
34 """Wrapper for notebook API calls."""
33 """Wrapper for contents API calls."""
35 def __init__(self, base_url):
34 def __init__(self, base_url):
36 self.base_url = base_url
35 self.base_url = base_url
37
36
38 def _req(self, verb, path, body=None):
37 def _req(self, verb, path, body=None):
39 response = requests.request(verb,
38 response = requests.request(verb,
40 url_path_join(self.base_url, 'api/notebooks', path),
39 url_path_join(self.base_url, 'api/contents', path),
41 data=body,
40 data=body,
42 )
41 )
43 response.raise_for_status()
42 response.raise_for_status()
@@ -49,8 +48,11 b' class NBAPI(object):'
49 def read(self, name, path='/'):
48 def read(self, name, path='/'):
50 return self._req('GET', url_path_join(path, name))
49 return self._req('GET', url_path_join(path, name))
51
50
52 def create_untitled(self, path='/'):
51 def create_untitled(self, path='/', ext=None):
53 return self._req('POST', path)
52 body = None
53 if ext:
54 body = json.dumps({'ext': ext})
55 return self._req('POST', path, body)
54
56
55 def upload_untitled(self, body, path='/'):
57 def upload_untitled(self, body, path='/'):
56 return self._req('POST', path, body)
58 return self._req('POST', path, body)
@@ -65,6 +67,9 b' class NBAPI(object):'
65 def upload(self, name, body, path='/'):
67 def upload(self, name, body, path='/'):
66 return self._req('PUT', url_path_join(path, name), body)
68 return self._req('PUT', url_path_join(path, name), body)
67
69
70 def mkdir(self, name, path='/'):
71 return self._req('PUT', url_path_join(path, name), json.dumps({'type': 'directory'}))
72
68 def copy(self, copy_from, copy_to, path='/'):
73 def copy(self, copy_from, copy_to, path='/'):
69 body = json.dumps({'copy_from':copy_from})
74 body = json.dumps({'copy_from':copy_from})
70 return self._req('PUT', url_path_join(path, copy_to), body)
75 return self._req('PUT', url_path_join(path, copy_to), body)
@@ -112,8 +117,20 b' class APITest(NotebookTestBase):'
112 del dirs[0] # remove ''
117 del dirs[0] # remove ''
113 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
118 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
114
119
120 @staticmethod
121 def _blob_for_name(name):
122 return name.encode('utf-8') + b'\xFF'
123
124 @staticmethod
125 def _txt_for_name(name):
126 return u'%s text file' % name
127
115 def setUp(self):
128 def setUp(self):
116 nbdir = self.notebook_dir.name
129 nbdir = self.notebook_dir.name
130 self.blob = os.urandom(100)
131 self.b64_blob = base64.encodestring(self.blob).decode('ascii')
132
133
117
134
118 for d in (self.dirs + self.hidden_dirs):
135 for d in (self.dirs + self.hidden_dirs):
119 d.replace('/', os.sep)
136 d.replace('/', os.sep)
@@ -122,12 +139,22 b' class APITest(NotebookTestBase):'
122
139
123 for d, name in self.dirs_nbs:
140 for d, name in self.dirs_nbs:
124 d = d.replace('/', os.sep)
141 d = d.replace('/', os.sep)
142 # create a notebook
125 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
143 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
126 encoding='utf-8') as f:
144 encoding='utf-8') as f:
127 nb = new_notebook(name=name)
145 nb = new_notebook(name=name)
128 write(nb, f, format='ipynb')
146 write(nb, f, format='ipynb')
129
147
130 self.nb_api = NBAPI(self.base_url())
148 # create a text file
149 with io.open(pjoin(nbdir, d, '%s.txt' % name), 'w',
150 encoding='utf-8') as f:
151 f.write(self._txt_for_name(name))
152
153 # create a binary file
154 with io.open(pjoin(nbdir, d, '%s.blob' % name), 'wb') as f:
155 f.write(self._blob_for_name(name))
156
157 self.api = API(self.base_url())
131
158
132 def tearDown(self):
159 def tearDown(self):
133 nbdir = self.notebook_dir.name
160 nbdir = self.notebook_dir.name
@@ -139,175 +166,287 b' class APITest(NotebookTestBase):'
139 os.unlink(pjoin(nbdir, 'inroot.ipynb'))
166 os.unlink(pjoin(nbdir, 'inroot.ipynb'))
140
167
141 def test_list_notebooks(self):
168 def test_list_notebooks(self):
142 nbs = notebooks_only(self.nb_api.list().json())
169 nbs = notebooks_only(self.api.list().json())
143 self.assertEqual(len(nbs), 1)
170 self.assertEqual(len(nbs), 1)
144 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
171 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
145
172
146 nbs = notebooks_only(self.nb_api.list('/Directory with spaces in/').json())
173 nbs = notebooks_only(self.api.list('/Directory with spaces in/').json())
147 self.assertEqual(len(nbs), 1)
174 self.assertEqual(len(nbs), 1)
148 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
175 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
149
176
150 nbs = notebooks_only(self.nb_api.list(u'/unicodé/').json())
177 nbs = notebooks_only(self.api.list(u'/unicodé/').json())
151 self.assertEqual(len(nbs), 1)
178 self.assertEqual(len(nbs), 1)
152 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
179 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
153 self.assertEqual(nbs[0]['path'], u'unicodé')
180 self.assertEqual(nbs[0]['path'], u'unicodé')
154
181
155 nbs = notebooks_only(self.nb_api.list('/foo/bar/').json())
182 nbs = notebooks_only(self.api.list('/foo/bar/').json())
156 self.assertEqual(len(nbs), 1)
183 self.assertEqual(len(nbs), 1)
157 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
184 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
158 self.assertEqual(nbs[0]['path'], 'foo/bar')
185 self.assertEqual(nbs[0]['path'], 'foo/bar')
159
186
160 nbs = notebooks_only(self.nb_api.list('foo').json())
187 nbs = notebooks_only(self.api.list('foo').json())
161 self.assertEqual(len(nbs), 4)
188 self.assertEqual(len(nbs), 4)
162 nbnames = { normalize('NFC', n['name']) for n in nbs }
189 nbnames = { normalize('NFC', n['name']) for n in nbs }
163 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb']
190 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb']
164 expected = { normalize('NFC', name) for name in expected }
191 expected = { normalize('NFC', name) for name in expected }
165 self.assertEqual(nbnames, expected)
192 self.assertEqual(nbnames, expected)
166
193
167 nbs = notebooks_only(self.nb_api.list('ordering').json())
194 nbs = notebooks_only(self.api.list('ordering').json())
168 nbnames = [n['name'] for n in nbs]
195 nbnames = [n['name'] for n in nbs]
169 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
196 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
170 self.assertEqual(nbnames, expected)
197 self.assertEqual(nbnames, expected)
171
198
172 def test_list_dirs(self):
199 def test_list_dirs(self):
173 dirs = dirs_only(self.nb_api.list().json())
200 dirs = dirs_only(self.api.list().json())
174 dir_names = {normalize('NFC', d['name']) for d in dirs}
201 dir_names = {normalize('NFC', d['name']) for d in dirs}
175 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
202 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
176
203
177 def test_list_nonexistant_dir(self):
204 def test_list_nonexistant_dir(self):
178 with assert_http_error(404):
205 with assert_http_error(404):
179 self.nb_api.list('nonexistant')
206 self.api.list('nonexistant')
180
207
181 def test_get_contents(self):
208 def test_get_nb_contents(self):
182 for d, name in self.dirs_nbs:
209 for d, name in self.dirs_nbs:
183 nb = self.nb_api.read('%s.ipynb' % name, d+'/').json()
210 nb = self.api.read('%s.ipynb' % name, d+'/').json()
184 self.assertEqual(nb['name'], u'%s.ipynb' % name)
211 self.assertEqual(nb['name'], u'%s.ipynb' % name)
212 self.assertEqual(nb['type'], 'notebook')
213 self.assertIn('content', nb)
214 self.assertEqual(nb['format'], 'json')
185 self.assertIn('content', nb)
215 self.assertIn('content', nb)
186 self.assertIn('metadata', nb['content'])
216 self.assertIn('metadata', nb['content'])
187 self.assertIsInstance(nb['content']['metadata'], dict)
217 self.assertIsInstance(nb['content']['metadata'], dict)
188
218
219 def test_get_contents_no_such_file(self):
220 # Name that doesn't exist - should be a 404
221 with assert_http_error(404):
222 self.api.read('q.ipynb', 'foo')
223
224 def test_get_text_file_contents(self):
225 for d, name in self.dirs_nbs:
226 model = self.api.read(u'%s.txt' % name, d+'/').json()
227 self.assertEqual(model['name'], u'%s.txt' % name)
228 self.assertIn('content', model)
229 self.assertEqual(model['format'], 'text')
230 self.assertEqual(model['type'], 'file')
231 self.assertEqual(model['content'], self._txt_for_name(name))
232
233 # Name that doesn't exist - should be a 404
234 with assert_http_error(404):
235 self.api.read('q.txt', 'foo')
236
237 def test_get_binary_file_contents(self):
238 for d, name in self.dirs_nbs:
239 model = self.api.read(u'%s.blob' % name, d+'/').json()
240 self.assertEqual(model['name'], u'%s.blob' % name)
241 self.assertIn('content', model)
242 self.assertEqual(model['format'], 'base64')
243 self.assertEqual(model['type'], 'file')
244 b64_data = base64.encodestring(self._blob_for_name(name)).decode('ascii')
245 self.assertEqual(model['content'], b64_data)
246
189 # Name that doesn't exist - should be a 404
247 # Name that doesn't exist - should be a 404
190 with assert_http_error(404):
248 with assert_http_error(404):
191 self.nb_api.read('q.ipynb', 'foo')
249 self.api.read('q.txt', 'foo')
192
250
193 def _check_nb_created(self, resp, name, path):
251 def _check_created(self, resp, name, path, type='notebook'):
194 self.assertEqual(resp.status_code, 201)
252 self.assertEqual(resp.status_code, 201)
195 location_header = py3compat.str_to_unicode(resp.headers['Location'])
253 location_header = py3compat.str_to_unicode(resp.headers['Location'])
196 self.assertEqual(location_header, url_escape(url_path_join(u'/api/notebooks', path, name)))
254 self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path, name)))
197 self.assertEqual(resp.json()['name'], name)
255 rjson = resp.json()
198 assert os.path.isfile(pjoin(
256 self.assertEqual(rjson['name'], name)
257 self.assertEqual(rjson['path'], path)
258 self.assertEqual(rjson['type'], type)
259 isright = os.path.isdir if type == 'directory' else os.path.isfile
260 assert isright(pjoin(
199 self.notebook_dir.name,
261 self.notebook_dir.name,
200 path.replace('/', os.sep),
262 path.replace('/', os.sep),
201 name,
263 name,
202 ))
264 ))
203
265
204 def test_create_untitled(self):
266 def test_create_untitled(self):
205 resp = self.nb_api.create_untitled(path=u'å b')
267 resp = self.api.create_untitled(path=u'å b')
206 self._check_nb_created(resp, 'Untitled0.ipynb', u'å b')
268 self._check_created(resp, 'Untitled0.ipynb', u'å b')
207
269
208 # Second time
270 # Second time
209 resp = self.nb_api.create_untitled(path=u'å b')
271 resp = self.api.create_untitled(path=u'å b')
210 self._check_nb_created(resp, 'Untitled1.ipynb', u'å b')
272 self._check_created(resp, 'Untitled1.ipynb', u'å b')
211
273
212 # And two directories down
274 # And two directories down
213 resp = self.nb_api.create_untitled(path='foo/bar')
275 resp = self.api.create_untitled(path='foo/bar')
214 self._check_nb_created(resp, 'Untitled0.ipynb', 'foo/bar')
276 self._check_created(resp, 'Untitled0.ipynb', 'foo/bar')
277
278 def test_create_untitled_txt(self):
279 resp = self.api.create_untitled(path='foo/bar', ext='.txt')
280 self._check_created(resp, 'untitled0.txt', 'foo/bar', type='file')
281
282 resp = self.api.read(path='foo/bar', name='untitled0.txt')
283 model = resp.json()
284 self.assertEqual(model['type'], 'file')
285 self.assertEqual(model['format'], 'text')
286 self.assertEqual(model['content'], '')
215
287
216 def test_upload_untitled(self):
288 def test_upload_untitled(self):
217 nb = new_notebook(name='Upload test')
289 nb = new_notebook(name='Upload test')
218 nbmodel = {'content': nb}
290 nbmodel = {'content': nb, 'type': 'notebook'}
219 resp = self.nb_api.upload_untitled(path=u'å b',
291 resp = self.api.upload_untitled(path=u'å b',
220 body=json.dumps(nbmodel))
292 body=json.dumps(nbmodel))
221 self._check_nb_created(resp, 'Untitled0.ipynb', u'å b')
293 self._check_created(resp, 'Untitled0.ipynb', u'å b')
222
294
223 def test_upload(self):
295 def test_upload(self):
224 nb = new_notebook(name=u'ignored')
296 nb = new_notebook(name=u'ignored')
225 nbmodel = {'content': nb}
297 nbmodel = {'content': nb, 'type': 'notebook'}
226 resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
298 resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
227 body=json.dumps(nbmodel))
299 body=json.dumps(nbmodel))
228 self._check_nb_created(resp, u'Upload tést.ipynb', u'å b')
300 self._check_created(resp, u'Upload tést.ipynb', u'å b')
301
302 def test_mkdir(self):
303 resp = self.api.mkdir(u'New ∂ir', path=u'å b')
304 self._check_created(resp, u'New ∂ir', u'å b', type='directory')
305
306 def test_mkdir_hidden_400(self):
307 with assert_http_error(400):
308 resp = self.api.mkdir(u'.hidden', path=u'å b')
309
310 def test_upload_txt(self):
311 body = u'ünicode téxt'
312 model = {
313 'content' : body,
314 'format' : 'text',
315 'type' : 'file',
316 }
317 resp = self.api.upload(u'Upload tést.txt', path=u'å b',
318 body=json.dumps(model))
319
320 # check roundtrip
321 resp = self.api.read(path=u'å b', name=u'Upload tést.txt')
322 model = resp.json()
323 self.assertEqual(model['type'], 'file')
324 self.assertEqual(model['format'], 'text')
325 self.assertEqual(model['content'], body)
326
327 def test_upload_b64(self):
328 body = b'\xFFblob'
329 b64body = base64.encodestring(body).decode('ascii')
330 model = {
331 'content' : b64body,
332 'format' : 'base64',
333 'type' : 'file',
334 }
335 resp = self.api.upload(u'Upload tést.blob', path=u'å b',
336 body=json.dumps(model))
337
338 # check roundtrip
339 resp = self.api.read(path=u'å b', name=u'Upload tést.blob')
340 model = resp.json()
341 self.assertEqual(model['type'], 'file')
342 self.assertEqual(model['format'], 'base64')
343 decoded = base64.decodestring(model['content'].encode('ascii'))
344 self.assertEqual(decoded, body)
229
345
230 def test_upload_v2(self):
346 def test_upload_v2(self):
231 nb = v2.new_notebook()
347 nb = v2.new_notebook()
232 ws = v2.new_worksheet()
348 ws = v2.new_worksheet()
233 nb.worksheets.append(ws)
349 nb.worksheets.append(ws)
234 ws.cells.append(v2.new_code_cell(input='print("hi")'))
350 ws.cells.append(v2.new_code_cell(input='print("hi")'))
235 nbmodel = {'content': nb}
351 nbmodel = {'content': nb, 'type': 'notebook'}
236 resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
352 resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
237 body=json.dumps(nbmodel))
353 body=json.dumps(nbmodel))
238 self._check_nb_created(resp, u'Upload tést.ipynb', u'å b')
354 self._check_created(resp, u'Upload tést.ipynb', u'å b')
239 resp = self.nb_api.read(u'Upload tést.ipynb', u'å b')
355 resp = self.api.read(u'Upload tést.ipynb', u'å b')
240 data = resp.json()
356 data = resp.json()
241 self.assertEqual(data['content']['nbformat'], current.nbformat)
357 self.assertEqual(data['content']['nbformat'], current.nbformat)
242 self.assertEqual(data['content']['orig_nbformat'], 2)
358 self.assertEqual(data['content']['orig_nbformat'], 2)
243
359
244 def test_copy_untitled(self):
360 def test_copy_untitled(self):
245 resp = self.nb_api.copy_untitled(u'ç d.ipynb', path=u'å b')
361 resp = self.api.copy_untitled(u'ç d.ipynb', path=u'å b')
246 self._check_nb_created(resp, u'ç d-Copy0.ipynb', u'å b')
362 self._check_created(resp, u'ç d-Copy0.ipynb', u'å b')
247
363
248 def test_copy(self):
364 def test_copy(self):
249 resp = self.nb_api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
365 resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
250 self._check_nb_created(resp, u'cøpy.ipynb', u'å b')
366 self._check_created(resp, u'cøpy.ipynb', u'å b')
367
368 def test_copy_path(self):
369 resp = self.api.copy(u'foo/a.ipynb', u'cøpyfoo.ipynb', path=u'å b')
370 self._check_created(resp, u'cøpyfoo.ipynb', u'å b')
371
372 def test_copy_dir_400(self):
373 # can't copy directories
374 with assert_http_error(400):
375 resp = self.api.copy(u'å b', u'å c')
251
376
252 def test_delete(self):
377 def test_delete(self):
253 for d, name in self.dirs_nbs:
378 for d, name in self.dirs_nbs:
254 resp = self.nb_api.delete('%s.ipynb' % name, d)
379 resp = self.api.delete('%s.ipynb' % name, d)
255 self.assertEqual(resp.status_code, 204)
380 self.assertEqual(resp.status_code, 204)
256
381
257 for d in self.dirs + ['/']:
382 for d in self.dirs + ['/']:
258 nbs = notebooks_only(self.nb_api.list(d).json())
383 nbs = notebooks_only(self.api.list(d).json())
259 self.assertEqual(len(nbs), 0)
384 self.assertEqual(len(nbs), 0)
260
385
386 def test_delete_dirs(self):
387 # depth-first delete everything, so we don't try to delete empty directories
388 for name in sorted(self.dirs + ['/'], key=len, reverse=True):
389 listing = self.api.list(name).json()['content']
390 for model in listing:
391 self.api.delete(model['name'], model['path'])
392 listing = self.api.list('/').json()['content']
393 self.assertEqual(listing, [])
394
395 def test_delete_non_empty_dir(self):
396 """delete non-empty dir raises 400"""
397 with assert_http_error(400):
398 self.api.delete(u'å b')
399
261 def test_rename(self):
400 def test_rename(self):
262 resp = self.nb_api.rename('a.ipynb', 'foo', 'z.ipynb')
401 resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb')
263 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
402 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
264 self.assertEqual(resp.json()['name'], 'z.ipynb')
403 self.assertEqual(resp.json()['name'], 'z.ipynb')
265 assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
404 assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
266
405
267 nbs = notebooks_only(self.nb_api.list('foo').json())
406 nbs = notebooks_only(self.api.list('foo').json())
268 nbnames = set(n['name'] for n in nbs)
407 nbnames = set(n['name'] for n in nbs)
269 self.assertIn('z.ipynb', nbnames)
408 self.assertIn('z.ipynb', nbnames)
270 self.assertNotIn('a.ipynb', nbnames)
409 self.assertNotIn('a.ipynb', nbnames)
271
410
272 def test_rename_existing(self):
411 def test_rename_existing(self):
273 with assert_http_error(409):
412 with assert_http_error(409):
274 self.nb_api.rename('a.ipynb', 'foo', 'b.ipynb')
413 self.api.rename('a.ipynb', 'foo', 'b.ipynb')
275
414
276 def test_save(self):
415 def test_save(self):
277 resp = self.nb_api.read('a.ipynb', 'foo')
416 resp = self.api.read('a.ipynb', 'foo')
278 nbcontent = json.loads(resp.text)['content']
417 nbcontent = json.loads(resp.text)['content']
279 nb = to_notebook_json(nbcontent)
418 nb = to_notebook_json(nbcontent)
280 ws = new_worksheet()
419 ws = new_worksheet()
281 nb.worksheets = [ws]
420 nb.worksheets = [ws]
282 ws.cells.append(new_heading_cell(u'Created by test ³'))
421 ws.cells.append(new_heading_cell(u'Created by test ³'))
283
422
284 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
423 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
285 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
424 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
286
425
287 nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
426 nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
288 with io.open(nbfile, 'r', encoding='utf-8') as f:
427 with io.open(nbfile, 'r', encoding='utf-8') as f:
289 newnb = read(f, format='ipynb')
428 newnb = read(f, format='ipynb')
290 self.assertEqual(newnb.worksheets[0].cells[0].source,
429 self.assertEqual(newnb.worksheets[0].cells[0].source,
291 u'Created by test ³')
430 u'Created by test ³')
292 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
431 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
293 newnb = to_notebook_json(nbcontent)
432 newnb = to_notebook_json(nbcontent)
294 self.assertEqual(newnb.worksheets[0].cells[0].source,
433 self.assertEqual(newnb.worksheets[0].cells[0].source,
295 u'Created by test ³')
434 u'Created by test ³')
296
435
297 # Save and rename
436 # Save and rename
298 nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb}
437 nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb, 'type': 'notebook'}
299 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
438 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
300 saved = resp.json()
439 saved = resp.json()
301 self.assertEqual(saved['name'], 'a2.ipynb')
440 self.assertEqual(saved['name'], 'a2.ipynb')
302 self.assertEqual(saved['path'], 'foo/bar')
441 self.assertEqual(saved['path'], 'foo/bar')
303 assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
442 assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
304 assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
443 assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
305 with assert_http_error(404):
444 with assert_http_error(404):
306 self.nb_api.read('a.ipynb', 'foo')
445 self.api.read('a.ipynb', 'foo')
307
446
308 def test_checkpoints(self):
447 def test_checkpoints(self):
309 resp = self.nb_api.read('a.ipynb', 'foo')
448 resp = self.api.read('a.ipynb', 'foo')
310 r = self.nb_api.new_checkpoint('a.ipynb', 'foo')
449 r = self.api.new_checkpoint('a.ipynb', 'foo')
311 self.assertEqual(r.status_code, 201)
450 self.assertEqual(r.status_code, 201)
312 cp1 = r.json()
451 cp1 = r.json()
313 self.assertEqual(set(cp1), {'id', 'last_modified'})
452 self.assertEqual(set(cp1), {'id', 'last_modified'})
@@ -321,27 +460,26 b' class APITest(NotebookTestBase):'
321 hcell = new_heading_cell('Created by test')
460 hcell = new_heading_cell('Created by test')
322 ws.cells.append(hcell)
461 ws.cells.append(hcell)
323 # Save
462 # Save
324 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
463 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'}
325 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
464 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
326
465
327 # List checkpoints
466 # List checkpoints
328 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
467 cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
329 self.assertEqual(cps, [cp1])
468 self.assertEqual(cps, [cp1])
330
469
331 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
470 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
332 nb = to_notebook_json(nbcontent)
471 nb = to_notebook_json(nbcontent)
333 self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
472 self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
334
473
335 # Restore cp1
474 # Restore cp1
336 r = self.nb_api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
475 r = self.api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
337 self.assertEqual(r.status_code, 204)
476 self.assertEqual(r.status_code, 204)
338 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
477 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
339 nb = to_notebook_json(nbcontent)
478 nb = to_notebook_json(nbcontent)
340 self.assertEqual(nb.worksheets, [])
479 self.assertEqual(nb.worksheets, [])
341
480
342 # Delete cp1
481 # Delete cp1
343 r = self.nb_api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
482 r = self.api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
344 self.assertEqual(r.status_code, 204)
483 self.assertEqual(r.status_code, 204)
345 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
484 cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
346 self.assertEqual(cps, [])
485 self.assertEqual(cps, [])
347
@@ -15,74 +15,74 b' from IPython.utils.tempdir import TemporaryDirectory'
15 from IPython.utils.traitlets import TraitError
15 from IPython.utils.traitlets import TraitError
16 from IPython.html.utils import url_path_join
16 from IPython.html.utils import url_path_join
17
17
18 from ..filenbmanager import FileNotebookManager
18 from ..filemanager import FileContentsManager
19 from ..nbmanager import NotebookManager
19 from ..manager import ContentsManager
20
20
21
21
22 class TestFileNotebookManager(TestCase):
22 class TestFileContentsManager(TestCase):
23
23
24 def test_nb_dir(self):
24 def test_root_dir(self):
25 with TemporaryDirectory() as td:
25 with TemporaryDirectory() as td:
26 fm = FileNotebookManager(notebook_dir=td)
26 fm = FileContentsManager(root_dir=td)
27 self.assertEqual(fm.notebook_dir, td)
27 self.assertEqual(fm.root_dir, td)
28
28
29 def test_missing_nb_dir(self):
29 def test_missing_root_dir(self):
30 with TemporaryDirectory() as td:
30 with TemporaryDirectory() as td:
31 nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
31 root = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
32 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir)
32 self.assertRaises(TraitError, FileContentsManager, root_dir=root)
33
33
34 def test_invalid_nb_dir(self):
34 def test_invalid_root_dir(self):
35 with NamedTemporaryFile() as tf:
35 with NamedTemporaryFile() as tf:
36 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=tf.name)
36 self.assertRaises(TraitError, FileContentsManager, root_dir=tf.name)
37
37
38 def test_get_os_path(self):
38 def test_get_os_path(self):
39 # full filesystem path should be returned with correct operating system
39 # full filesystem path should be returned with correct operating system
40 # separators.
40 # separators.
41 with TemporaryDirectory() as td:
41 with TemporaryDirectory() as td:
42 nbdir = td
42 root = td
43 fm = FileNotebookManager(notebook_dir=nbdir)
43 fm = FileContentsManager(root_dir=root)
44 path = fm._get_os_path('test.ipynb', '/path/to/notebook/')
44 path = fm._get_os_path('test.ipynb', '/path/to/notebook/')
45 rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
45 rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
46 fs_path = os.path.join(fm.notebook_dir, *rel_path_list)
46 fs_path = os.path.join(fm.root_dir, *rel_path_list)
47 self.assertEqual(path, fs_path)
47 self.assertEqual(path, fs_path)
48
48
49 fm = FileNotebookManager(notebook_dir=nbdir)
49 fm = FileContentsManager(root_dir=root)
50 path = fm._get_os_path('test.ipynb')
50 path = fm._get_os_path('test.ipynb')
51 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
51 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
52 self.assertEqual(path, fs_path)
52 self.assertEqual(path, fs_path)
53
53
54 fm = FileNotebookManager(notebook_dir=nbdir)
54 fm = FileContentsManager(root_dir=root)
55 path = fm._get_os_path('test.ipynb', '////')
55 path = fm._get_os_path('test.ipynb', '////')
56 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
56 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
57 self.assertEqual(path, fs_path)
57 self.assertEqual(path, fs_path)
58
58
59 def test_checkpoint_subdir(self):
59 def test_checkpoint_subdir(self):
60 subd = u'sub ∂ir'
60 subd = u'sub ∂ir'
61 cp_name = 'test-cp.ipynb'
61 cp_name = 'test-cp.ipynb'
62 with TemporaryDirectory() as td:
62 with TemporaryDirectory() as td:
63 nbdir = td
63 root = td
64 os.mkdir(os.path.join(td, subd))
64 os.mkdir(os.path.join(td, subd))
65 fm = FileNotebookManager(notebook_dir=nbdir)
65 fm = FileContentsManager(root_dir=root)
66 cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/')
66 cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/')
67 cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd)
67 cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd)
68 self.assertNotEqual(cp_dir, cp_subdir)
68 self.assertNotEqual(cp_dir, cp_subdir)
69 self.assertEqual(cp_dir, os.path.join(nbdir, fm.checkpoint_dir, cp_name))
69 self.assertEqual(cp_dir, os.path.join(root, fm.checkpoint_dir, cp_name))
70 self.assertEqual(cp_subdir, os.path.join(nbdir, subd, fm.checkpoint_dir, cp_name))
70 self.assertEqual(cp_subdir, os.path.join(root, subd, fm.checkpoint_dir, cp_name))
71
71
72
73 class TestContentsManager(TestCase):
72
74
73 class TestNotebookManager(TestCase):
74
75 def setUp(self):
75 def setUp(self):
76 self._temp_dir = TemporaryDirectory()
76 self._temp_dir = TemporaryDirectory()
77 self.td = self._temp_dir.name
77 self.td = self._temp_dir.name
78 self.notebook_manager = FileNotebookManager(
78 self.contents_manager = FileContentsManager(
79 notebook_dir=self.td,
79 root_dir=self.td,
80 log=logging.getLogger()
80 log=logging.getLogger()
81 )
81 )
82
82
83 def tearDown(self):
83 def tearDown(self):
84 self._temp_dir.cleanup()
84 self._temp_dir.cleanup()
85
85
86 def make_dir(self, abs_path, rel_path):
86 def make_dir(self, abs_path, rel_path):
87 """make subdirectory, rel_path is the relative path
87 """make subdirectory, rel_path is the relative path
88 to that directory from the location where the server started"""
88 to that directory from the location where the server started"""
@@ -91,31 +91,31 b' class TestNotebookManager(TestCase):'
91 os.makedirs(os_path)
91 os.makedirs(os_path)
92 except OSError:
92 except OSError:
93 print("Directory already exists: %r" % os_path)
93 print("Directory already exists: %r" % os_path)
94
94
95 def add_code_cell(self, nb):
95 def add_code_cell(self, nb):
96 output = current.new_output("display_data", output_javascript="alert('hi');")
96 output = current.new_output("display_data", output_javascript="alert('hi');")
97 cell = current.new_code_cell("print('hi')", outputs=[output])
97 cell = current.new_code_cell("print('hi')", outputs=[output])
98 if not nb.worksheets:
98 if not nb.worksheets:
99 nb.worksheets.append(current.new_worksheet())
99 nb.worksheets.append(current.new_worksheet())
100 nb.worksheets[0].cells.append(cell)
100 nb.worksheets[0].cells.append(cell)
101
101
102 def new_notebook(self):
102 def new_notebook(self):
103 nbm = self.notebook_manager
103 cm = self.contents_manager
104 model = nbm.create_notebook()
104 model = cm.create_file()
105 name = model['name']
105 name = model['name']
106 path = model['path']
106 path = model['path']
107
107
108 full_model = nbm.get_notebook(name, path)
108 full_model = cm.get_model(name, path)
109 nb = full_model['content']
109 nb = full_model['content']
110 self.add_code_cell(nb)
110 self.add_code_cell(nb)
111
111
112 nbm.save_notebook(full_model, name, path)
112 cm.save(full_model, name, path)
113 return nb, name, path
113 return nb, name, path
114
114
115 def test_create_notebook(self):
115 def test_create_file(self):
116 nm = self.notebook_manager
116 cm = self.contents_manager
117 # Test in root directory
117 # Test in root directory
118 model = nm.create_notebook()
118 model = cm.create_file()
119 assert isinstance(model, dict)
119 assert isinstance(model, dict)
120 self.assertIn('name', model)
120 self.assertIn('name', model)
121 self.assertIn('path', model)
121 self.assertIn('path', model)
@@ -124,23 +124,23 b' class TestNotebookManager(TestCase):'
124
124
125 # Test in sub-directory
125 # Test in sub-directory
126 sub_dir = '/foo/'
126 sub_dir = '/foo/'
127 self.make_dir(nm.notebook_dir, 'foo')
127 self.make_dir(cm.root_dir, 'foo')
128 model = nm.create_notebook(None, sub_dir)
128 model = cm.create_file(None, sub_dir)
129 assert isinstance(model, dict)
129 assert isinstance(model, dict)
130 self.assertIn('name', model)
130 self.assertIn('name', model)
131 self.assertIn('path', model)
131 self.assertIn('path', model)
132 self.assertEqual(model['name'], 'Untitled0.ipynb')
132 self.assertEqual(model['name'], 'Untitled0.ipynb')
133 self.assertEqual(model['path'], sub_dir.strip('/'))
133 self.assertEqual(model['path'], sub_dir.strip('/'))
134
134
135 def test_get_notebook(self):
135 def test_get(self):
136 nm = self.notebook_manager
136 cm = self.contents_manager
137 # Create a notebook
137 # Create a notebook
138 model = nm.create_notebook()
138 model = cm.create_file()
139 name = model['name']
139 name = model['name']
140 path = model['path']
140 path = model['path']
141
141
142 # Check that we 'get' on the notebook we just created
142 # Check that we 'get' on the notebook we just created
143 model2 = nm.get_notebook(name, path)
143 model2 = cm.get_model(name, path)
144 assert isinstance(model2, dict)
144 assert isinstance(model2, dict)
145 self.assertIn('name', model2)
145 self.assertIn('name', model2)
146 self.assertIn('path', model2)
146 self.assertIn('path', model2)
@@ -149,66 +149,66 b' class TestNotebookManager(TestCase):'
149
149
150 # Test in sub-directory
150 # Test in sub-directory
151 sub_dir = '/foo/'
151 sub_dir = '/foo/'
152 self.make_dir(nm.notebook_dir, 'foo')
152 self.make_dir(cm.root_dir, 'foo')
153 model = nm.create_notebook(None, sub_dir)
153 model = cm.create_file(None, sub_dir)
154 model2 = nm.get_notebook(name, sub_dir)
154 model2 = cm.get_model(name, sub_dir)
155 assert isinstance(model2, dict)
155 assert isinstance(model2, dict)
156 self.assertIn('name', model2)
156 self.assertIn('name', model2)
157 self.assertIn('path', model2)
157 self.assertIn('path', model2)
158 self.assertIn('content', model2)
158 self.assertIn('content', model2)
159 self.assertEqual(model2['name'], 'Untitled0.ipynb')
159 self.assertEqual(model2['name'], 'Untitled0.ipynb')
160 self.assertEqual(model2['path'], sub_dir.strip('/'))
160 self.assertEqual(model2['path'], sub_dir.strip('/'))
161
161
162 def test_update_notebook(self):
162 def test_update(self):
163 nm = self.notebook_manager
163 cm = self.contents_manager
164 # Create a notebook
164 # Create a notebook
165 model = nm.create_notebook()
165 model = cm.create_file()
166 name = model['name']
166 name = model['name']
167 path = model['path']
167 path = model['path']
168
168
169 # Change the name in the model for rename
169 # Change the name in the model for rename
170 model['name'] = 'test.ipynb'
170 model['name'] = 'test.ipynb'
171 model = nm.update_notebook(model, name, path)
171 model = cm.update(model, name, path)
172 assert isinstance(model, dict)
172 assert isinstance(model, dict)
173 self.assertIn('name', model)
173 self.assertIn('name', model)
174 self.assertIn('path', model)
174 self.assertIn('path', model)
175 self.assertEqual(model['name'], 'test.ipynb')
175 self.assertEqual(model['name'], 'test.ipynb')
176
176
177 # Make sure the old name is gone
177 # Make sure the old name is gone
178 self.assertRaises(HTTPError, nm.get_notebook, name, path)
178 self.assertRaises(HTTPError, cm.get_model, name, path)
179
179
180 # Test in sub-directory
180 # Test in sub-directory
181 # Create a directory and notebook in that directory
181 # Create a directory and notebook in that directory
182 sub_dir = '/foo/'
182 sub_dir = '/foo/'
183 self.make_dir(nm.notebook_dir, 'foo')
183 self.make_dir(cm.root_dir, 'foo')
184 model = nm.create_notebook(None, sub_dir)
184 model = cm.create_file(None, sub_dir)
185 name = model['name']
185 name = model['name']
186 path = model['path']
186 path = model['path']
187
187
188 # Change the name in the model for rename
188 # Change the name in the model for rename
189 model['name'] = 'test_in_sub.ipynb'
189 model['name'] = 'test_in_sub.ipynb'
190 model = nm.update_notebook(model, name, path)
190 model = cm.update(model, name, path)
191 assert isinstance(model, dict)
191 assert isinstance(model, dict)
192 self.assertIn('name', model)
192 self.assertIn('name', model)
193 self.assertIn('path', model)
193 self.assertIn('path', model)
194 self.assertEqual(model['name'], 'test_in_sub.ipynb')
194 self.assertEqual(model['name'], 'test_in_sub.ipynb')
195 self.assertEqual(model['path'], sub_dir.strip('/'))
195 self.assertEqual(model['path'], sub_dir.strip('/'))
196
196
197 # Make sure the old name is gone
197 # Make sure the old name is gone
198 self.assertRaises(HTTPError, nm.get_notebook, name, path)
198 self.assertRaises(HTTPError, cm.get_model, name, path)
199
199
200 def test_save_notebook(self):
200 def test_save(self):
201 nm = self.notebook_manager
201 cm = self.contents_manager
202 # Create a notebook
202 # Create a notebook
203 model = nm.create_notebook()
203 model = cm.create_file()
204 name = model['name']
204 name = model['name']
205 path = model['path']
205 path = model['path']
206
206
207 # Get the model with 'content'
207 # Get the model with 'content'
208 full_model = nm.get_notebook(name, path)
208 full_model = cm.get_model(name, path)
209
209
210 # Save the notebook
210 # Save the notebook
211 model = nm.save_notebook(full_model, name, path)
211 model = cm.save(full_model, name, path)
212 assert isinstance(model, dict)
212 assert isinstance(model, dict)
213 self.assertIn('name', model)
213 self.assertIn('name', model)
214 self.assertIn('path', model)
214 self.assertIn('path', model)
@@ -218,103 +218,84 b' class TestNotebookManager(TestCase):'
218 # Test in sub-directory
218 # Test in sub-directory
219 # Create a directory and notebook in that directory
219 # Create a directory and notebook in that directory
220 sub_dir = '/foo/'
220 sub_dir = '/foo/'
221 self.make_dir(nm.notebook_dir, 'foo')
221 self.make_dir(cm.root_dir, 'foo')
222 model = nm.create_notebook(None, sub_dir)
222 model = cm.create_file(None, sub_dir)
223 name = model['name']
223 name = model['name']
224 path = model['path']
224 path = model['path']
225 model = nm.get_notebook(name, path)
225 model = cm.get_model(name, path)
226
226
227 # Change the name in the model for rename
227 # Change the name in the model for rename
228 model = nm.save_notebook(model, name, path)
228 model = cm.save(model, name, path)
229 assert isinstance(model, dict)
229 assert isinstance(model, dict)
230 self.assertIn('name', model)
230 self.assertIn('name', model)
231 self.assertIn('path', model)
231 self.assertIn('path', model)
232 self.assertEqual(model['name'], 'Untitled0.ipynb')
232 self.assertEqual(model['name'], 'Untitled0.ipynb')
233 self.assertEqual(model['path'], sub_dir.strip('/'))
233 self.assertEqual(model['path'], sub_dir.strip('/'))
234
234
235 def test_save_notebook_with_script(self):
235 def test_delete(self):
236 nm = self.notebook_manager
236 cm = self.contents_manager
237 # Create a notebook
238 model = nm.create_notebook()
239 nm.save_script = True
240 model = nm.create_notebook()
241 name = model['name']
242 path = model['path']
243
244 # Get the model with 'content'
245 full_model = nm.get_notebook(name, path)
246
247 # Save the notebook
248 model = nm.save_notebook(full_model, name, path)
249
250 # Check that the script was created
251 py_path = os.path.join(nm.notebook_dir, os.path.splitext(name)[0]+'.py')
252 assert os.path.exists(py_path), py_path
253
254 def test_delete_notebook(self):
255 nm = self.notebook_manager
256 # Create a notebook
237 # Create a notebook
257 nb, name, path = self.new_notebook()
238 nb, name, path = self.new_notebook()
258
239
259 # Delete the notebook
240 # Delete the notebook
260 nm.delete_notebook(name, path)
241 cm.delete(name, path)
261
242
262 # Check that a 'get' on the deleted notebook raises and error
243 # Check that a 'get' on the deleted notebook raises and error
263 self.assertRaises(HTTPError, nm.get_notebook, name, path)
244 self.assertRaises(HTTPError, cm.get_model, name, path)
264
245
265 def test_copy_notebook(self):
246 def test_copy(self):
266 nm = self.notebook_manager
247 cm = self.contents_manager
267 path = u'å b'
248 path = u'å b'
268 name = u'nb √.ipynb'
249 name = u'nb √.ipynb'
269 os.mkdir(os.path.join(nm.notebook_dir, path))
250 os.mkdir(os.path.join(cm.root_dir, path))
270 orig = nm.create_notebook({'name' : name}, path=path)
251 orig = cm.create_file({'name' : name}, path=path)
271
252
272 # copy with unspecified name
253 # copy with unspecified name
273 copy = nm.copy_notebook(name, path=path)
254 copy = cm.copy(name, path=path)
274 self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb'))
255 self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb'))
275
256
276 # copy with specified name
257 # copy with specified name
277 copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path)
258 copy2 = cm.copy(name, u'copy 2.ipynb', path=path)
278 self.assertEqual(copy2['name'], u'copy 2.ipynb')
259 self.assertEqual(copy2['name'], u'copy 2.ipynb')
279
260
280 def test_trust_notebook(self):
261 def test_trust_notebook(self):
281 nbm = self.notebook_manager
262 cm = self.contents_manager
282 nb, name, path = self.new_notebook()
263 nb, name, path = self.new_notebook()
283
264
284 untrusted = nbm.get_notebook(name, path)['content']
265 untrusted = cm.get_model(name, path)['content']
285 assert not nbm.notary.check_cells(untrusted)
266 assert not cm.notary.check_cells(untrusted)
286
267
287 # print(untrusted)
268 # print(untrusted)
288 nbm.trust_notebook(name, path)
269 cm.trust_notebook(name, path)
289 trusted = nbm.get_notebook(name, path)['content']
270 trusted = cm.get_model(name, path)['content']
290 # print(trusted)
271 # print(trusted)
291 assert nbm.notary.check_cells(trusted)
272 assert cm.notary.check_cells(trusted)
292
273
293 def test_mark_trusted_cells(self):
274 def test_mark_trusted_cells(self):
294 nbm = self.notebook_manager
275 cm = self.contents_manager
295 nb, name, path = self.new_notebook()
276 nb, name, path = self.new_notebook()
296
277
297 nbm.mark_trusted_cells(nb, name, path)
278 cm.mark_trusted_cells(nb, name, path)
298 for cell in nb.worksheets[0].cells:
279 for cell in nb.worksheets[0].cells:
299 if cell.cell_type == 'code':
280 if cell.cell_type == 'code':
300 assert not cell.trusted
281 assert not cell.trusted
301
282
302 nbm.trust_notebook(name, path)
283 cm.trust_notebook(name, path)
303 nb = nbm.get_notebook(name, path)['content']
284 nb = cm.get_model(name, path)['content']
304 for cell in nb.worksheets[0].cells:
285 for cell in nb.worksheets[0].cells:
305 if cell.cell_type == 'code':
286 if cell.cell_type == 'code':
306 assert cell.trusted
287 assert cell.trusted
307
288
308 def test_check_and_sign(self):
289 def test_check_and_sign(self):
309 nbm = self.notebook_manager
290 cm = self.contents_manager
310 nb, name, path = self.new_notebook()
291 nb, name, path = self.new_notebook()
311
292
312 nbm.mark_trusted_cells(nb, name, path)
293 cm.mark_trusted_cells(nb, name, path)
313 nbm.check_and_sign(nb, name, path)
294 cm.check_and_sign(nb, name, path)
314 assert not nbm.notary.check_signature(nb)
295 assert not cm.notary.check_signature(nb)
315
296
316 nbm.trust_notebook(name, path)
297 cm.trust_notebook(name, path)
317 nb = nbm.get_notebook(name, path)['content']
298 nb = cm.get_model(name, path)['content']
318 nbm.mark_trusted_cells(nb, name, path)
299 cm.mark_trusted_cells(nb, name, path)
319 nbm.check_and_sign(nb, name, path)
300 cm.check_and_sign(nb, name, path)
320 assert nbm.notary.check_signature(nb)
301 assert cm.notary.check_signature(nb)
@@ -27,8 +27,16 b' class MainKernelHandler(IPythonHandler):'
27 @web.authenticated
27 @web.authenticated
28 @json_errors
28 @json_errors
29 def post(self):
29 def post(self):
30 model = self.get_json_body()
31 if model is None:
32 raise web.HTTPError(400, "No JSON data provided")
33 try:
34 name = model['name']
35 except KeyError:
36 raise web.HTTPError(400, "Missing field in JSON data: name")
37
30 km = self.kernel_manager
38 km = self.kernel_manager
31 kernel_id = km.start_kernel()
39 kernel_id = km.start_kernel(kernel_name=name)
32 model = km.kernel_model(kernel_id)
40 model = km.kernel_model(kernel_id)
33 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
41 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
34 self.set_header('Location', url_escape(location))
42 self.set_header('Location', url_escape(location))
@@ -72,8 +72,8 b' class MappingKernelManager(MultiKernelManager):'
72 os_path = os.path.dirname(os_path)
72 os_path = os.path.dirname(os_path)
73 return os_path
73 return os_path
74
74
75 def start_kernel(self, kernel_id=None, path=None, **kwargs):
75 def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
76 """Start a kernel for a session an return its kernel_id.
76 """Start a kernel for a session and return its kernel_id.
77
77
78 Parameters
78 Parameters
79 ----------
79 ----------
@@ -84,12 +84,16 b' class MappingKernelManager(MultiKernelManager):'
84 path : API path
84 path : API path
85 The API path (unicode, '/' delimited) for the cwd.
85 The API path (unicode, '/' delimited) for the cwd.
86 Will be transformed to an OS path relative to root_dir.
86 Will be transformed to an OS path relative to root_dir.
87 kernel_name : str
88 The name identifying which kernel spec to launch. This is ignored if
89 an existing kernel is returned, but it may be checked in the future.
87 """
90 """
88 if kernel_id is None:
91 if kernel_id is None:
89 kwargs['extra_arguments'] = self.kernel_argv
92 kwargs['extra_arguments'] = self.kernel_argv
90 if path is not None:
93 if path is not None:
91 kwargs['cwd'] = self.cwd_for_path(path)
94 kwargs['cwd'] = self.cwd_for_path(path)
92 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
95 kernel_id = super(MappingKernelManager, self).start_kernel(
96 kernel_name=kernel_name, **kwargs)
93 self.log.info("Kernel started: %s" % kernel_id)
97 self.log.info("Kernel started: %s" % kernel_id)
94 self.log.debug("Kernel args: %r" % kwargs)
98 self.log.debug("Kernel args: %r" % kwargs)
95 # register callback for failed auto-restart
99 # register callback for failed auto-restart
@@ -111,7 +115,8 b' class MappingKernelManager(MultiKernelManager):'
111 """Return a dictionary of kernel information described in the
115 """Return a dictionary of kernel information described in the
112 JSON standard model."""
116 JSON standard model."""
113 self._check_kernel_id(kernel_id)
117 self._check_kernel_id(kernel_id)
114 model = {"id":kernel_id}
118 model = {"id":kernel_id,
119 "name": self._kernels[kernel_id].kernel_name}
115 return model
120 return model
116
121
117 def list_kernels(self):
122 def list_kernels(self):
@@ -1,6 +1,6 b''
1 """Test the kernels service API."""
1 """Test the kernels service API."""
2
2
3
3 import json
4 import requests
4 import requests
5
5
6 from IPython.html.utils import url_path_join
6 from IPython.html.utils import url_path_join
@@ -30,8 +30,9 b' class KernelAPI(object):'
30 def get(self, id):
30 def get(self, id):
31 return self._req('GET', id)
31 return self._req('GET', id)
32
32
33 def start(self):
33 def start(self, name='python'):
34 return self._req('POST', '')
34 body = json.dumps({'name': name})
35 return self._req('POST', '', body)
35
36
36 def shutdown(self, id):
37 def shutdown(self, id):
37 return self._req('DELETE', id)
38 return self._req('DELETE', id)
@@ -64,11 +65,14 b' class KernelAPITest(NotebookTestBase):'
64 self.assertEqual(r.status_code, 201)
65 self.assertEqual(r.status_code, 201)
65 self.assertIsInstance(kern1, dict)
66 self.assertIsInstance(kern1, dict)
66
67
68 self.assertEqual(r.headers['x-frame-options'], "SAMEORIGIN")
69
67 # GET request
70 # GET request
68 r = self.kern_api.list()
71 r = self.kern_api.list()
69 self.assertEqual(r.status_code, 200)
72 self.assertEqual(r.status_code, 200)
70 assert isinstance(r.json(), list)
73 assert isinstance(r.json(), list)
71 self.assertEqual(r.json()[0]['id'], kern1['id'])
74 self.assertEqual(r.json()[0]['id'], kern1['id'])
75 self.assertEqual(r.json()[0]['name'], kern1['name'])
72
76
73 # create another kernel and check that they both are added to the
77 # create another kernel and check that they both are added to the
74 # list of kernels from a GET request
78 # list of kernels from a GET request
@@ -89,6 +93,7 b' class KernelAPITest(NotebookTestBase):'
89 self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id'])
93 self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id'])
90 rekern = r.json()
94 rekern = r.json()
91 self.assertEqual(rekern['id'], kern2['id'])
95 self.assertEqual(rekern['id'], kern2['id'])
96 self.assertEqual(rekern['name'], kern2['name'])
92
97
93 def test_kernel_handler(self):
98 def test_kernel_handler(self):
94 # GET kernel with given id
99 # GET kernel with given id
@@ -7,6 +7,8 b' from tornado import web'
7
7
8 from ...base.handlers import IPythonHandler, json_errors
8 from ...base.handlers import IPythonHandler, json_errors
9
9
10 from IPython.kernel.kernelspec import _pythonfirst
11
10
12
11 class MainKernelSpecHandler(IPythonHandler):
13 class MainKernelSpecHandler(IPythonHandler):
12 SUPPORTED_METHODS = ('GET',)
14 SUPPORTED_METHODS = ('GET',)
@@ -16,7 +18,7 b' class MainKernelSpecHandler(IPythonHandler):'
16 def get(self):
18 def get(self):
17 ksm = self.kernel_spec_manager
19 ksm = self.kernel_spec_manager
18 results = []
20 results = []
19 for kernel_name in ksm.find_kernel_specs():
21 for kernel_name in sorted(ksm.find_kernel_specs(), key=_pythonfirst):
20 d = ksm.get_kernel_spec(kernel_name).to_dict()
22 d = ksm.get_kernel_spec(kernel_name).to_dict()
21 d['name'] = kernel_name
23 d['name'] = kernel_name
22 results.append(d)
24 results.append(d)
@@ -1,20 +1,7 b''
1 """Tornado handlers for the sessions web service.
1 """Tornado handlers for the sessions web service."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Zach Sailer
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 import json
6 import json
20
7
@@ -24,10 +11,6 b' from ...base.handlers import IPythonHandler, json_errors'
24 from IPython.utils.jsonutil import date_default
11 from IPython.utils.jsonutil import date_default
25 from IPython.html.utils import url_path_join, url_escape
12 from IPython.html.utils import url_path_join, url_escape
26
13
27 #-----------------------------------------------------------------------------
28 # Session web service handlers
29 #-----------------------------------------------------------------------------
30
31
14
32 class SessionRootHandler(IPythonHandler):
15 class SessionRootHandler(IPythonHandler):
33
16
@@ -45,27 +28,30 b' class SessionRootHandler(IPythonHandler):'
45 # Creates a new session
28 # Creates a new session
46 #(unless a session already exists for the named nb)
29 #(unless a session already exists for the named nb)
47 sm = self.session_manager
30 sm = self.session_manager
48 nbm = self.notebook_manager
31 cm = self.contents_manager
49 km = self.kernel_manager
32 km = self.kernel_manager
33
50 model = self.get_json_body()
34 model = self.get_json_body()
51 if model is None:
35 if model is None:
52 raise web.HTTPError(400, "No JSON data provided")
36 raise web.HTTPError(400, "No JSON data provided")
53 try:
37 try:
54 name = model['notebook']['name']
38 name = model['notebook']['name']
55 except KeyError:
39 except KeyError:
56 raise web.HTTPError(400, "Missing field in JSON data: name")
40 raise web.HTTPError(400, "Missing field in JSON data: notebook.name")
57 try:
41 try:
58 path = model['notebook']['path']
42 path = model['notebook']['path']
59 except KeyError:
43 except KeyError:
60 raise web.HTTPError(400, "Missing field in JSON data: path")
44 raise web.HTTPError(400, "Missing field in JSON data: notebook.path")
45 try:
46 kernel_name = model['kernel']['name']
47 except KeyError:
48 raise web.HTTPError(400, "Missing field in JSON data: kernel.name")
49
61 # Check to see if session exists
50 # Check to see if session exists
62 if sm.session_exists(name=name, path=path):
51 if sm.session_exists(name=name, path=path):
63 model = sm.get_session(name=name, path=path)
52 model = sm.get_session(name=name, path=path)
64 else:
53 else:
65 # allow nbm to specify kernels cwd
54 model = sm.create_session(name=name, path=path, kernel_name=kernel_name)
66 kernel_path = nbm.get_kernel_path(name=name, path=path)
67 kernel_id = km.start_kernel(path=kernel_path)
68 model = sm.create_session(name=name, path=path, kernel_id=kernel_id)
69 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
55 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
70 self.set_header('Location', url_escape(location))
56 self.set_header('Location', url_escape(location))
71 self.set_status(201)
57 self.set_status(201)
@@ -108,10 +94,7 b' class SessionHandler(IPythonHandler):'
108 def delete(self, session_id):
94 def delete(self, session_id):
109 # Deletes the session with given session_id
95 # Deletes the session with given session_id
110 sm = self.session_manager
96 sm = self.session_manager
111 km = self.kernel_manager
112 session = sm.get_session(session_id=session_id)
113 sm.delete_session(session_id)
97 sm.delete_session(session_id)
114 km.shutdown_kernel(session['kernel']['id'])
115 self.set_status(204)
98 self.set_status(204)
116 self.finish()
99 self.finish()
117
100
@@ -23,12 +23,16 b' from tornado import web'
23
23
24 from IPython.config.configurable import LoggingConfigurable
24 from IPython.config.configurable import LoggingConfigurable
25 from IPython.utils.py3compat import unicode_type
25 from IPython.utils.py3compat import unicode_type
26 from IPython.utils.traitlets import Instance
26
27
27 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
28 # Classes
29 # Classes
29 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
30
31
31 class SessionManager(LoggingConfigurable):
32 class SessionManager(LoggingConfigurable):
33
34 kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
35 contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=())
32
36
33 # Session database initialized below
37 # Session database initialized below
34 _cursor = None
38 _cursor = None
@@ -69,10 +73,15 b' class SessionManager(LoggingConfigurable):'
69 "Create a uuid for a new session"
73 "Create a uuid for a new session"
70 return unicode_type(uuid.uuid4())
74 return unicode_type(uuid.uuid4())
71
75
72 def create_session(self, name=None, path=None, kernel_id=None):
76 def create_session(self, name=None, path=None, kernel_name='python'):
73 """Creates a session and returns its model"""
77 """Creates a session and returns its model"""
74 session_id = self.new_session_id()
78 session_id = self.new_session_id()
75 return self.save_session(session_id, name=name, path=path, kernel_id=kernel_id)
79 # allow nbm to specify kernels cwd
80 kernel_path = self.contents_manager.get_kernel_path(name=name, path=path)
81 kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
82 kernel_name=kernel_name)
83 return self.save_session(session_id, name=name, path=path,
84 kernel_id=kernel_id)
76
85
77 def save_session(self, session_id, name=None, path=None, kernel_id=None):
86 def save_session(self, session_id, name=None, path=None, kernel_id=None):
78 """Saves the items for the session with the given session_id
87 """Saves the items for the session with the given session_id
@@ -170,8 +179,7 b' class SessionManager(LoggingConfigurable):'
170 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
179 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
171 self.cursor.execute(query, list(kwargs.values()) + [session_id])
180 self.cursor.execute(query, list(kwargs.values()) + [session_id])
172
181
173 @staticmethod
182 def row_factory(self, cursor, row):
174 def row_factory(cursor, row):
175 """Takes sqlite database session row and turns it into a dictionary"""
183 """Takes sqlite database session row and turns it into a dictionary"""
176 row = sqlite3.Row(cursor, row)
184 row = sqlite3.Row(cursor, row)
177 model = {
185 model = {
@@ -180,9 +188,7 b' class SessionManager(LoggingConfigurable):'
180 'name': row['name'],
188 'name': row['name'],
181 'path': row['path']
189 'path': row['path']
182 },
190 },
183 'kernel': {
191 'kernel': self.kernel_manager.kernel_model(row['kernel_id'])
184 'id': row['kernel_id'],
185 }
186 }
192 }
187 return model
193 return model
188
194
@@ -195,5 +201,6 b' class SessionManager(LoggingConfigurable):'
195 def delete_session(self, session_id):
201 def delete_session(self, session_id):
196 """Deletes the row in the session database with given session_id"""
202 """Deletes the row in the session database with given session_id"""
197 # Check that session exists before deleting
203 # Check that session exists before deleting
198 self.get_session(session_id=session_id)
204 session = self.get_session(session_id=session_id)
205 self.kernel_manager.shutdown_kernel(session['kernel']['id'])
199 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
206 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
@@ -5,79 +5,101 b' from unittest import TestCase'
5 from tornado import web
5 from tornado import web
6
6
7 from ..sessionmanager import SessionManager
7 from ..sessionmanager import SessionManager
8 from IPython.html.services.kernels.kernelmanager import MappingKernelManager
9
10 class DummyKernel(object):
11 def __init__(self, kernel_name='python'):
12 self.kernel_name = kernel_name
13
14 class DummyMKM(MappingKernelManager):
15 """MappingKernelManager interface that doesn't start kernels, for testing"""
16 def __init__(self, *args, **kwargs):
17 super(DummyMKM, self).__init__(*args, **kwargs)
18 self.id_letters = iter(u'ABCDEFGHIJK')
19
20 def _new_id(self):
21 return next(self.id_letters)
22
23 def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
24 kernel_id = kernel_id or self._new_id()
25 self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name)
26 return kernel_id
27
28 def shutdown_kernel(self, kernel_id, now=False):
29 del self._kernels[kernel_id]
8
30
9 class TestSessionManager(TestCase):
31 class TestSessionManager(TestCase):
10
32
11 def test_get_session(self):
33 def test_get_session(self):
12 sm = SessionManager()
34 sm = SessionManager(kernel_manager=DummyMKM())
13 session_id = sm.new_session_id()
35 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
14 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
36 kernel_name='bar')['id']
15 model = sm.get_session(session_id=session_id)
37 model = sm.get_session(session_id=session_id)
16 expected = {'id':session_id, 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}}
38 expected = {'id':session_id,
39 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'},
40 'kernel': {'id':u'A', 'name': 'bar'}}
17 self.assertEqual(model, expected)
41 self.assertEqual(model, expected)
18
42
19 def test_bad_get_session(self):
43 def test_bad_get_session(self):
20 # Should raise error if a bad key is passed to the database.
44 # Should raise error if a bad key is passed to the database.
21 sm = SessionManager()
45 sm = SessionManager(kernel_manager=DummyMKM())
22 session_id = sm.new_session_id()
46 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
23 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
47 kernel_name='foo')['id']
24 self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword
48 self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword
25
49
26 def test_list_sessions(self):
50 def test_list_sessions(self):
27 sm = SessionManager()
51 sm = SessionManager(kernel_manager=DummyMKM())
28 session_id1 = sm.new_session_id()
52 sessions = [
29 session_id2 = sm.new_session_id()
53 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
30 session_id3 = sm.new_session_id()
54 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
31 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678')
55 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
32 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678')
56 ]
33 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678')
34 sessions = sm.list_sessions()
57 sessions = sm.list_sessions()
35 expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
58 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
36 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}},
59 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
37 {'id':session_id2, 'notebook': {'name':u'test2.ipynb',
60 {'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb',
38 'path': u'/path/to/2/'}, 'kernel':{'id':u'5678'}},
61 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}},
39 {'id':session_id3, 'notebook':{'name':u'test3.ipynb',
62 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
40 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}]
63 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
41 self.assertEqual(sessions, expected)
64 self.assertEqual(sessions, expected)
42
65
43 def test_update_session(self):
66 def test_update_session(self):
44 sm = SessionManager()
67 sm = SessionManager(kernel_manager=DummyMKM())
45 session_id = sm.new_session_id()
68 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
46 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id=None)
69 kernel_name='julia')['id']
47 sm.update_session(session_id, kernel_id='5678')
48 sm.update_session(session_id, name='new_name.ipynb')
70 sm.update_session(session_id, name='new_name.ipynb')
49 model = sm.get_session(session_id=session_id)
71 model = sm.get_session(session_id=session_id)
50 expected = {'id':session_id, 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}}
72 expected = {'id':session_id,
73 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'},
74 'kernel':{'id':u'A', 'name':'julia'}}
51 self.assertEqual(model, expected)
75 self.assertEqual(model, expected)
52
76
53 def test_bad_update_session(self):
77 def test_bad_update_session(self):
54 # try to update a session with a bad keyword ~ raise error
78 # try to update a session with a bad keyword ~ raise error
55 sm = SessionManager()
79 sm = SessionManager(kernel_manager=DummyMKM())
56 session_id = sm.new_session_id()
80 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
57 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
81 kernel_name='ir')['id']
58 self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
82 self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
59
83
60 def test_delete_session(self):
84 def test_delete_session(self):
61 sm = SessionManager()
85 sm = SessionManager(kernel_manager=DummyMKM())
62 session_id1 = sm.new_session_id()
86 sessions = [
63 session_id2 = sm.new_session_id()
87 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
64 session_id3 = sm.new_session_id()
88 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
65 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678')
89 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
66 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678')
90 ]
67 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678')
91 sm.delete_session(sessions[1]['id'])
68 sm.delete_session(session_id2)
92 new_sessions = sm.list_sessions()
69 sessions = sm.list_sessions()
93 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
70 expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
94 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
71 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}},
95 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
72 {'id':session_id3, 'notebook':{'name':u'test3.ipynb',
96 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
73 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}]
97 self.assertEqual(new_sessions, expected)
74 self.assertEqual(sessions, expected)
75
98
76 def test_bad_delete_session(self):
99 def test_bad_delete_session(self):
77 # try to delete a session that doesn't exist ~ raise error
100 # try to delete a session that doesn't exist ~ raise error
78 sm = SessionManager()
101 sm = SessionManager(kernel_manager=DummyMKM())
79 session_id = sm.new_session_id()
102 sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python')
80 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
81 self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
103 self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
82 self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
104 self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
83
105
@@ -37,8 +37,9 b' class SessionAPI(object):'
37 def get(self, id):
37 def get(self, id):
38 return self._req('GET', id)
38 return self._req('GET', id)
39
39
40 def create(self, name, path):
40 def create(self, name, path, kernel_name='python'):
41 body = json.dumps({'notebook': {'name':name, 'path':path}})
41 body = json.dumps({'notebook': {'name':name, 'path':path},
42 'kernel': {'name': kernel_name}})
42 return self._req('POST', '', body)
43 return self._req('POST', '', body)
43
44
44 def modify(self, id, name, path):
45 def modify(self, id, name, path):
@@ -1,21 +1,12 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 var ipython = ipython || {};
9 // On document ready
5 require(['base/js/page'], function(page) {
10 //============================================================================
6 var page_instance = new page.Page();
11
12
13 $(document).ready(function () {
14
15 IPython.page = new IPython.Page();
16 $('button#login_submit').addClass("btn btn-default");
7 $('button#login_submit').addClass("btn btn-default");
17 IPython.page.show();
8 page_instance.show();
18 $('input#password_input').focus();
9 $('input#password_input').focus();
19
10
11 ipython.page = page_instance;
20 });
12 });
21
@@ -1,43 +1,35 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'base/js/utils',
7
7 'jquery',
8 //============================================================================
8 ], function(IPython, utils, $){
9 // Login button
10 //============================================================================
11
12 var IPython = (function (IPython) {
13 "use strict";
9 "use strict";
14
10
15 var LoginWidget = function (selector, options) {
11 var LoginWidget = function (selector, options) {
16 options = options || {};
12 options = options || {};
17 this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl");
13 this.base_url = options.base_url || utils.get_body_data("baseUrl");
18 this.selector = selector;
14 this.selector = selector;
19 if (this.selector !== undefined) {
15 if (this.selector !== undefined) {
20 this.element = $(selector);
16 this.element = $(selector);
21 this.style();
22 this.bind_events();
17 this.bind_events();
23 }
18 }
24 };
19 };
25
20
26 LoginWidget.prototype.style = function () {
27 this.element.find("button").addClass("btn btn-default btn-sm");
28 };
29
21
30
22
31 LoginWidget.prototype.bind_events = function () {
23 LoginWidget.prototype.bind_events = function () {
32 var that = this;
24 var that = this;
33 this.element.find("button#logout").click(function () {
25 this.element.find("button#logout").click(function () {
34 window.location = IPython.utils.url_join_encode(
26 window.location = utils.url_join_encode(
35 that.base_url,
27 that.base_url,
36 "logout"
28 "logout"
37 );
29 );
38 });
30 });
39 this.element.find("button#login").click(function () {
31 this.element.find("button#login").click(function () {
40 window.location = IPython.utils.url_join_encode(
32 window.location = utils.url_join_encode(
41 that.base_url,
33 that.base_url,
42 "login"
34 "login"
43 );
35 );
@@ -47,6 +39,5 b' var IPython = (function (IPython) {'
47 // Set module variables
39 // Set module variables
48 IPython.LoginWidget = LoginWidget;
40 IPython.LoginWidget = LoginWidget;
49
41
50 return IPython;
42 return {'LoginWidget': LoginWidget};
51
43 });
52 }(IPython));
@@ -1,20 +1,10 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 var ipython = ipython || {};
9 // On document ready
5 require(['base/js/page'], function(page) {
10 //============================================================================
6 var page_instance = new page.Page();
11
7 page_instance.show();
12
13 $(document).ready(function () {
14
15 IPython.page = new IPython.Page();
16 $('#ipython-main-app').addClass('border-box-sizing');
17 IPython.page.show();
18
8
9 ipython.page = page_instance;
19 });
10 });
20
@@ -1,2 +1,7 b''
1 /*!
2 *
3 * IPython auth
4 *
5 */
1 @import "login.less";
6 @import "login.less";
2 @import "logout.less"; No newline at end of file
7 @import "logout.less";
@@ -1,20 +1,14 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2013 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Utility for modal dialogs with bootstrap
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 ], function(IPython, $) {
12 IPython.namespace('IPython.dialog');
13
14 IPython.dialog = (function (IPython) {
15 "use strict";
8 "use strict";
16
9
17 var modal = function (options) {
10 var modal = function (options) {
11
18 var modal = $("<div/>")
12 var modal = $("<div/>")
19 .addClass("modal")
13 .addClass("modal")
20 .addClass("fade")
14 .addClass("fade")
@@ -79,26 +73,28 b' IPython.dialog = (function (IPython) {'
79 });
73 });
80 }
74 }
81 modal.on("hidden.bs.modal", function () {
75 modal.on("hidden.bs.modal", function () {
82 if (IPython.notebook) {
76 if (options.notebook) {
83 var cell = IPython.notebook.get_selected_cell();
77 var cell = options.notebook.get_selected_cell();
84 if (cell) cell.select();
78 if (cell) cell.select();
85 IPython.keyboard_manager.enable();
79 }
86 IPython.keyboard_manager.command_mode();
80 if (options.keyboard_manager) {
81 options.keyboard_manager.enable();
82 options.keyboard_manager.command_mode();
87 }
83 }
88 });
84 });
89
85
90 if (IPython.keyboard_manager) {
86 if (options.keyboard_manager) {
91 IPython.keyboard_manager.disable();
87 options.keyboard_manager.disable();
92 }
88 }
93
89
94 return modal.modal(options);
90 return modal.modal(options);
95 };
91 };
96
92
97 var edit_metadata = function (md, callback, name) {
93 var edit_metadata = function (options) {
98 name = name || "Cell";
94 options.name = options.name || "Cell";
99 var error_div = $('<div/>').css('color', 'red');
95 var error_div = $('<div/>').css('color', 'red');
100 var message =
96 var message =
101 "Manually edit the JSON below to manipulate the metadata for this " + name + "." +
97 "Manually edit the JSON below to manipulate the metadata for this " + options.name + "." +
102 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
98 " We recommend putting custom metadata attributes in an appropriately named sub-structure," +
103 " so they don't conflict with those of others.";
99 " so they don't conflict with those of others.";
104
100
@@ -106,7 +102,7 b' IPython.dialog = (function (IPython) {'
106 .attr('rows', '13')
102 .attr('rows', '13')
107 .attr('cols', '80')
103 .attr('cols', '80')
108 .attr('name', 'metadata')
104 .attr('name', 'metadata')
109 .text(JSON.stringify(md || {}, null, 2));
105 .text(JSON.stringify(options.md || {}, null, 2));
110
106
111 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
107 var dialogform = $('<div/>').attr('title', 'Edit the metadata')
112 .append(
108 .append(
@@ -128,8 +124,8 b' IPython.dialog = (function (IPython) {'
128 autoIndent: true,
124 autoIndent: true,
129 mode: 'application/json',
125 mode: 'application/json',
130 });
126 });
131 var modal = IPython.dialog.modal({
127 var modal_obj = modal({
132 title: "Edit " + name + " Metadata",
128 title: "Edit " + options.name + " Metadata",
133 body: dialogform,
129 body: dialogform,
134 buttons: {
130 buttons: {
135 OK: { class : "btn-primary",
131 OK: { class : "btn-primary",
@@ -143,19 +139,25 b' IPython.dialog = (function (IPython) {'
143 error_div.text('WARNING: Could not save invalid JSON.');
139 error_div.text('WARNING: Could not save invalid JSON.');
144 return false;
140 return false;
145 }
141 }
146 callback(new_md);
142 options.callback(new_md);
147 }
143 }
148 },
144 },
149 Cancel: {}
145 Cancel: {}
150 }
146 },
147 notebook: options.notebook,
148 keyboard_manager: options.keyboard_manager,
151 });
149 });
152
150
153 modal.on('shown.bs.modal', function(){ editor.refresh(); });
151 modal_obj.on('shown.bs.modal', function(){ editor.refresh(); });
154 };
152 };
155
153
156 return {
154 var dialog = {
157 modal : modal,
155 modal : modal,
158 edit_metadata : edit_metadata,
156 edit_metadata : edit_metadata,
159 };
157 };
160
158
161 }(IPython));
159 // Backwards compatability.
160 IPython.dialog = dialog;
161
162 return dialog;
163 });
@@ -1,32 +1,24 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Events
10 //============================================================================
11
3
12 // Give us an object to bind all events to. This object should be created
4 // Give us an object to bind all events to. This object should be created
13 // before all other objects so it exists when others register event handlers.
5 // before all other objects so it exists when others register event handlers.
14 // To trigger an event handler:
6 // To register an event handler:
15 // $([IPython.events]).trigger('event.Namespace');
7 //
16 // To handle it:
8 // require(['base/js/events'], function (events) {
17 // $([IPython.events]).on('event.Namespace',function () {});
9 // events.on("event.Namespace", function () { do_stuff(); });
10 // });
18
11
19 var IPython = (function (IPython) {
12 define(['base/js/namespace', 'jquery'], function(IPython, $) {
20 "use strict";
13 "use strict";
21
14
22 var utils = IPython.utils;
23
24 var Events = function () {};
15 var Events = function () {};
25
16
17 var events = new Events();
18
19 // Backwards compatability.
26 IPython.Events = Events;
20 IPython.Events = Events;
27 IPython.events = new Events();
21 IPython.events = events;
28
22
29 return IPython;
23 return $([events]);
30
24 });
31 }(IPython));
32
@@ -1,19 +1,14 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Keyboard management
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 'base/js/utils',
12 IPython.namespace('IPython.keyboard');
8 ], function(IPython, $, utils) {
13
14 IPython.keyboard = (function (IPython) {
15 "use strict";
9 "use strict";
16
10
11
17 // Setup global keycodes and inverse keycodes.
12 // Setup global keycodes and inverse keycodes.
18
13
19 // See http://unixpapa.com/js/key.html for a complete description. The short of
14 // See http://unixpapa.com/js/key.html for a complete description. The short of
@@ -51,8 +46,8 b' IPython.keyboard = (function (IPython) {'
51 '; :': 186, '= +': 187, '- _': 189
46 '; :': 186, '= +': 187, '- _': 189
52 };
47 };
53
48
54 var browser = IPython.utils.browser[0];
49 var browser = utils.browser[0];
55 var platform = IPython.utils.platform;
50 var platform = utils.platform;
56
51
57 if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
52 if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') {
58 $.extend(_keycodes, _mozilla_keycodes);
53 $.extend(_keycodes, _mozilla_keycodes);
@@ -130,18 +125,19 b' IPython.keyboard = (function (IPython) {'
130
125
131 // Shortcut manager class
126 // Shortcut manager class
132
127
133 var ShortcutManager = function (delay) {
128 var ShortcutManager = function (delay, events) {
134 this._shortcuts = {};
129 this._shortcuts = {};
135 this._counts = {};
130 this._counts = {};
136 this._timers = {};
131 this._timers = {};
137 this.delay = delay || 800; // delay in milliseconds
132 this.delay = delay || 800; // delay in milliseconds
133 this.events = events;
138 };
134 };
139
135
140 ShortcutManager.prototype.help = function () {
136 ShortcutManager.prototype.help = function () {
141 var help = [];
137 var help = [];
142 for (var shortcut in this._shortcuts) {
138 for (var shortcut in this._shortcuts) {
143 var help_string = this._shortcuts[shortcut]['help'];
139 var help_string = this._shortcuts[shortcut].help;
144 var help_index = this._shortcuts[shortcut]['help_index'];
140 var help_index = this._shortcuts[shortcut].help_index;
145 if (help_string) {
141 if (help_string) {
146 if (platform === 'MacOS') {
142 if (platform === 'MacOS') {
147 shortcut = shortcut.replace('meta', 'cmd');
143 shortcut = shortcut.replace('meta', 'cmd');
@@ -182,7 +178,7 b' IPython.keyboard = (function (IPython) {'
182 this._shortcuts[shortcut] = data;
178 this._shortcuts[shortcut] = data;
183 if (!suppress_help_update) {
179 if (!suppress_help_update) {
184 // update the keyboard shortcuts notebook help
180 // update the keyboard shortcuts notebook help
185 $([IPython.events]).trigger('rebuild.QuickHelp');
181 this.events.trigger('rebuild.QuickHelp');
186 }
182 }
187 };
183 };
188
184
@@ -191,7 +187,7 b' IPython.keyboard = (function (IPython) {'
191 this.add_shortcut(shortcut, data[shortcut], true);
187 this.add_shortcut(shortcut, data[shortcut], true);
192 }
188 }
193 // update the keyboard shortcuts notebook help
189 // update the keyboard shortcuts notebook help
194 $([IPython.events]).trigger('rebuild.QuickHelp');
190 this.events.trigger('rebuild.QuickHelp');
195 };
191 };
196
192
197 ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
193 ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
@@ -200,7 +196,7 b' IPython.keyboard = (function (IPython) {'
200 delete this._shortcuts[shortcut];
196 delete this._shortcuts[shortcut];
201 if (!suppress_help_update) {
197 if (!suppress_help_update) {
202 // update the keyboard shortcuts notebook help
198 // update the keyboard shortcuts notebook help
203 $([IPython.events]).trigger('rebuild.QuickHelp');
199 this.events.trigger('rebuild.QuickHelp');
204 }
200 }
205 };
201 };
206
202
@@ -211,7 +207,7 b' IPython.keyboard = (function (IPython) {'
211 var timer = null;
207 var timer = null;
212 if (c[shortcut] === data.count-1) {
208 if (c[shortcut] === data.count-1) {
213 c[shortcut] = 0;
209 c[shortcut] = 0;
214 var timer = t[shortcut];
210 timer = t[shortcut];
215 if (timer) {clearTimeout(timer); delete t[shortcut];}
211 if (timer) {clearTimeout(timer); delete t[shortcut];}
216 return data.handler(event);
212 return data.handler(event);
217 } else {
213 } else {
@@ -228,7 +224,7 b' IPython.keyboard = (function (IPython) {'
228 var shortcut = event_to_shortcut(event);
224 var shortcut = event_to_shortcut(event);
229 var data = this._shortcuts[shortcut];
225 var data = this._shortcuts[shortcut];
230 if (data) {
226 if (data) {
231 var handler = data['handler'];
227 var handler = data.handler;
232 if (handler) {
228 if (handler) {
233 if (data.count === 1) {
229 if (data.count === 1) {
234 return handler(event);
230 return handler(event);
@@ -243,10 +239,10 b' IPython.keyboard = (function (IPython) {'
243 ShortcutManager.prototype.handles = function (event) {
239 ShortcutManager.prototype.handles = function (event) {
244 var shortcut = event_to_shortcut(event);
240 var shortcut = event_to_shortcut(event);
245 var data = this._shortcuts[shortcut];
241 var data = this._shortcuts[shortcut];
246 return !( data === undefined || data.handler === undefined )
242 return !( data === undefined || data.handler === undefined );
247 }
243 };
248
244
249 return {
245 var keyboard = {
250 keycodes : keycodes,
246 keycodes : keycodes,
251 inv_keycodes : inv_keycodes,
247 inv_keycodes : inv_keycodes,
252 ShortcutManager : ShortcutManager,
248 ShortcutManager : ShortcutManager,
@@ -256,4 +252,8 b' IPython.keyboard = (function (IPython) {'
256 event_to_shortcut : event_to_shortcut
252 event_to_shortcut : event_to_shortcut
257 };
253 };
258
254
259 }(IPython));
255 // For backwards compatability.
256 IPython.keyboard = keyboard;
257
258 return keyboard;
259 });
@@ -1,34 +1,8 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 var IPython = IPython || {};
4 var IPython = IPython || {};
9
5 define([], function(){
10 IPython.version = "3.0.0-dev";
6 IPython.version = "3.0.0-dev";
11
7 return IPython;
12 IPython.namespace = function (ns_string) {
8 });
13 "use strict";
14
15 var parts = ns_string.split('.'),
16 parent = IPython,
17 i;
18
19 // String redundant leading global
20 if (parts[0] === "IPython") {
21 parts = parts.slice(1);
22 }
23
24 for (i=0; i<parts.length; i+=1) {
25 // Create property if it doesn't exist
26 if (typeof parent[parts[i]] === "undefined") {
27 parent[parts[i]] = {};
28 }
29 }
30 return parent;
31 };
32
33
34
@@ -1,32 +1,19 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Global header/site setup.
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 ], function(IPython, $){
12 var IPython = (function (IPython) {
13 "use strict";
8 "use strict";
14
9
15 var Page = function () {
10 var Page = function () {
16 this.style();
17 this.bind_events();
11 this.bind_events();
18 };
12 };
19
13
20 Page.prototype.style = function () {
21 $('div#header').addClass('border-box-sizing');
22 $('div#site').addClass('border-box-sizing');
23 };
24
25
26 Page.prototype.bind_events = function () {
14 Page.prototype.bind_events = function () {
27 };
15 };
28
16
29
30 Page.prototype.show = function () {
17 Page.prototype.show = function () {
31 // The header and site divs start out hidden to prevent FLOUC.
18 // The header and site divs start out hidden to prevent FLOUC.
32 // Main scripts should call this method after styling everything.
19 // Main scripts should call this method after styling everything.
@@ -34,23 +21,21 b' var IPython = (function (IPython) {'
34 this.show_site();
21 this.show_site();
35 };
22 };
36
23
37
38 Page.prototype.show_header = function () {
24 Page.prototype.show_header = function () {
39 // The header and site divs start out hidden to prevent FLOUC.
25 // The header and site divs start out hidden to prevent FLOUC.
40 // Main scripts should call this method after styling everything.
26 // Main scripts should call this method after styling everything.
27 // TODO: selector are hardcoded, pass as constructor argument
41 $('div#header').css('display','block');
28 $('div#header').css('display','block');
42 };
29 };
43
30
44
45 Page.prototype.show_site = function () {
31 Page.prototype.show_site = function () {
46 // The header and site divs start out hidden to prevent FLOUC.
32 // The header and site divs start out hidden to prevent FLOUC.
47 // Main scripts should call this method after styling everything.
33 // Main scripts should call this method after styling everything.
34 // TODO: selector are hardcoded, pass as constructor argument
48 $('div#site').css('display','block');
35 $('div#site').css('display','block');
49 };
36 };
50
37
51
38 // Register self in the global namespace for convenience.
52 IPython.Page = Page;
39 IPython.Page = Page;
53
40 return {'Page': Page};
54 return IPython;
41 });
55
56 }(IPython));
@@ -1,19 +1,12 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2014 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Utilities
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11 IPython.namespace('IPython.security');
7 'components/google-caja/html-css-sanitizer-minified',
12
8 ], function(IPython, $) {
13 IPython.security = (function (IPython) {
14 "use strict";
9 "use strict";
15
16 var utils = IPython.utils;
17
10
18 var noop = function (x) { return x; };
11 var noop = function (x) { return x; };
19
12
@@ -117,10 +110,12 b' IPython.security = (function (IPython) {'
117 return sanitized;
110 return sanitized;
118 };
111 };
119
112
120 return {
113 var security = {
121 caja: caja,
114 caja: caja,
122 sanitize_html: sanitize_html
115 sanitize_html: sanitize_html
123 };
116 };
124
117
125 }(IPython));
118 IPython.security = security;
126
119
120 return security;
121 });
@@ -1,13 +1,10 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 //============================================================================
4 define([
5 // Utilities
5 'base/js/namespace',
6 //============================================================================
6 'jquery',
7
7 ], function(IPython, $){
8 IPython.namespace('IPython.utils');
9
10 IPython.utils = (function (IPython) {
11 "use strict";
8 "use strict";
12
9
13 IPython.load_extensions = function () {
10 IPython.load_extensions = function () {
@@ -528,8 +525,8 b' IPython.utils = (function (IPython) {'
528 }
525 }
529 console.log(msg);
526 console.log(msg);
530 };
527 };
531
528
532 return {
529 var utils = {
533 regex_split : regex_split,
530 regex_split : regex_split,
534 uuid : uuid,
531 uuid : uuid,
535 fixConsole : fixConsole,
532 fixConsole : fixConsole,
@@ -553,5 +550,8 b' IPython.utils = (function (IPython) {'
553 log_ajax_error : log_ajax_error,
550 log_ajax_error : log_ajax_error,
554 };
551 };
555
552
556 }(IPython));
553 // Backwards compatability.
557
554 IPython.utils = utils;
555
556 return utils;
557 });
@@ -24,6 +24,7 b' div#header {'
24 padding-left: 30px;
24 padding-left: 30px;
25 padding-bottom: 5px;
25 padding-bottom: 5px;
26 border-bottom: 1px solid @navbar-default-border;
26 border-bottom: 1px solid @navbar-default-border;
27 .border-box-sizing();
27 }
28 }
28
29
29 #ipython_notebook {
30 #ipython_notebook {
@@ -50,6 +51,7 b' div#header {'
50 #site {
51 #site {
51 width: 100%;
52 width: 100%;
52 display: none;
53 display: none;
54 .border-box-sizing();
53 }
55 }
54
56
55 /* Smaller buttons */
57 /* Smaller buttons */
@@ -69,6 +71,14 b' span#login_widget {'
69 float: right;
71 float: right;
70 }
72 }
71
73
74 span#login_widget > .button,
75 #logout
76 {
77 .btn();
78 .btn-default();
79 .btn-sm();
80 }
81
72 .nav-header {
82 .nav-header {
73 text-transform: none;
83 text-transform: none;
74 }
84 }
@@ -93,3 +103,8 b' span#login_widget {'
93 }
103 }
94 }
104 }
95
105
106 // less mixin to be sure to add the right class to get icons with font awesome.
107 .icon(@ico){
108 .fa();
109 content: @ico;
110 }
@@ -1,3 +1,8 b''
1 /*!
2 *
3 * IPython base
4 *
5 */
1 @import "variables.less";
6 @import "variables.less";
2 @import "mixins.less";
7 @import "mixins.less";
3 @import "flexbox.less";
8 @import "flexbox.less";
@@ -1,1 +1,1 b''
1 Subproject commit 7c07f705d4f068828fb76f74ac7d4d355aea06f9
1 Subproject commit a80ac7a2f6d045e3903d3c9e189a10cc96255b05
@@ -12,12 +12,12 b''
12 *
12 *
13 * Same thing with `profile/static/custom/custom.css` to inject custom css into the notebook.
13 * Same thing with `profile/static/custom/custom.css` to inject custom css into the notebook.
14 *
14 *
15 * Example :
15 * __Example 1:__
16 *
16 *
17 * Create a custom button in toolbar that execute `%qtconsole` in kernel
17 * Create a custom button in toolbar that execute `%qtconsole` in kernel
18 * and hence open a qtconsole attached to the same kernel as the current notebook
18 * and hence open a qtconsole attached to the same kernel as the current notebook
19 *
19 *
20 * $([IPython.events]).on('app_initialized.NotebookApp', function(){
20 * IPython.events.on('app_initialized.NotebookApp', function(){
21 * IPython.toolbar.add_buttons_group([
21 * IPython.toolbar.add_buttons_group([
22 * {
22 * {
23 * 'label' : 'run qtconsole',
23 * 'label' : 'run qtconsole',
@@ -30,7 +30,16 b''
30 * ]);
30 * ]);
31 * });
31 * });
32 *
32 *
33 * Example :
33 * __Example 2:__
34 *
35 * At the completion of the dashboard loading, load an unofficial javascript extension
36 * that is installed in profile/static/custom/
37 *
38 * IPython.events.on('app_initialized.DashboardApp', function(){
39 * require(['custom/unofficial_extension.js'])
40 * });
41 *
42 * __Example 3:__
34 *
43 *
35 * Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );`
44 * Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );`
36 * to load custom script into the notebook.
45 * to load custom script into the notebook.
@@ -122,4 +122,4 b' dateFormat.i18n = {'
122 // For convenience...
122 // For convenience...
123 Date.prototype.format = function (mask, utc) {
123 Date.prototype.format = function (mask, utc) {
124 return dateFormat(this, mask, utc);
124 return dateFormat(this, mask, utc);
125 };
125 }; No newline at end of file
@@ -1,53 +1,61 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 'base/js/utils',
8 //============================================================================
8 ], function(IPython, $, utils) {
9 // Cell
9 // TODO: remove IPython dependency here
10 //============================================================================
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
14 * @namespace IPython
15 * @submodule Cell
16 */
17
18 var IPython = (function (IPython) {
19 "use strict";
10 "use strict";
20
11
21 var utils = IPython.utils;
12 // monkey patch CM to be able to syntax highlight cell magics
22 var keycodes = IPython.keyboard.keycodes;
13 // bug reported upstream,
14 // see https://github.com/marijnh/CodeMirror2/issues/670
15 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
16 CodeMirror.modes.null = function() {
17 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
18 };
19 }
23
20
24 /**
21 CodeMirror.patchedGetMode = function(config, mode){
25 * The Base `Cell` class from which to inherit
22 var cmmode = CodeMirror.getMode(config, mode);
26 * @class Cell
23 if(cmmode.indent === null) {
27 **/
24 console.log('patch mode "' , mode, '" on the fly');
25 cmmode.indent = function(){return 0;};
26 }
27 return cmmode;
28 };
29 // end monkey patching CodeMirror
28
30
29 /*
30 * @constructor
31 *
32 * @param {object|undefined} [options]
33 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
34 */
35 var Cell = function (options) {
31 var Cell = function (options) {
36
32 // Constructor
37 options = this.mergeopt(Cell, options);
33 //
34 // The Base `Cell` class from which to inherit.
35 //
36 // Parameters:
37 // options: dictionary
38 // Dictionary of keyword arguments.
39 // events: $(Events) instance
40 // config: dictionary
41 // keyboard_manager: KeyboardManager instance
42 options = options || {};
43 this.keyboard_manager = options.keyboard_manager;
44 this.events = options.events;
45 var config = this.mergeopt(Cell, options.config);
38 // superclass default overwrite our default
46 // superclass default overwrite our default
39
47
40 this.placeholder = options.placeholder || '';
48 this.placeholder = config.placeholder || '';
41 this.read_only = options.cm_config.readOnly;
49 this.read_only = config.cm_config.readOnly;
42 this.selected = false;
50 this.selected = false;
43 this.rendered = false;
51 this.rendered = false;
44 this.mode = 'command';
52 this.mode = 'command';
45 this.metadata = {};
53 this.metadata = {};
46 // load this from metadata later ?
54 // load this from metadata later ?
47 this.user_highlight = 'auto';
55 this.user_highlight = 'auto';
48 this.cm_config = options.cm_config;
56 this.cm_config = config.cm_config;
49 this.cell_id = utils.uuid();
57 this.cell_id = utils.uuid();
50 this._options = options;
58 this._options = config;
51
59
52 // For JS VM engines optimization, attributes should be all set (even
60 // For JS VM engines optimization, attributes should be all set (even
53 // to null) in the constructor, and if possible, if different subclass
61 // to null) in the constructor, and if possible, if different subclass
@@ -70,7 +78,12 b' var IPython = (function (IPython) {'
70 cm_config : {
78 cm_config : {
71 indentUnit : 4,
79 indentUnit : 4,
72 readOnly: false,
80 readOnly: false,
73 theme: "default"
81 theme: "default",
82 extraKeys: {
83 "Cmd-Right":"goLineRight",
84 "End":"goLineRight",
85 "Cmd-Left":"goLineLeft"
86 }
74 }
87 }
75 };
88 };
76
89
@@ -127,27 +140,27 b' var IPython = (function (IPython) {'
127 // We trigger events so that Cell doesn't have to depend on Notebook.
140 // We trigger events so that Cell doesn't have to depend on Notebook.
128 that.element.click(function (event) {
141 that.element.click(function (event) {
129 if (!that.selected) {
142 if (!that.selected) {
130 $([IPython.events]).trigger('select.Cell', {'cell':that});
143 that.events.trigger('select.Cell', {'cell':that});
131 }
144 }
132 });
145 });
133 that.element.focusin(function (event) {
146 that.element.focusin(function (event) {
134 if (!that.selected) {
147 if (!that.selected) {
135 $([IPython.events]).trigger('select.Cell', {'cell':that});
148 that.events.trigger('select.Cell', {'cell':that});
136 }
149 }
137 });
150 });
138 if (this.code_mirror) {
151 if (this.code_mirror) {
139 this.code_mirror.on("change", function(cm, change) {
152 this.code_mirror.on("change", function(cm, change) {
140 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
153 that.events.trigger("set_dirty.Notebook", {value: true});
141 });
154 });
142 }
155 }
143 if (this.code_mirror) {
156 if (this.code_mirror) {
144 this.code_mirror.on('focus', function(cm, change) {
157 this.code_mirror.on('focus', function(cm, change) {
145 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
158 that.events.trigger('edit_mode.Cell', {cell: that});
146 });
159 });
147 }
160 }
148 if (this.code_mirror) {
161 if (this.code_mirror) {
149 this.code_mirror.on('blur', function(cm, change) {
162 this.code_mirror.on('blur', function(cm, change) {
150 $([IPython.events]).trigger('command_mode.Cell', {cell: that});
163 that.events.trigger('command_mode.Cell', {cell: that});
151 });
164 });
152 }
165 }
153 };
166 };
@@ -165,8 +178,7 b' var IPython = (function (IPython) {'
165 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
178 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
166 */
179 */
167 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
180 Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
168 var that = this;
181 var shortcuts = this.keyboard_manager.edit_shortcuts;
169 var shortcuts = IPython.keyboard_manager.edit_shortcuts;
170
182
171 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
183 // if this is an edit_shortcuts shortcut, the global keyboard/shortcut
172 // manager will handle it
184 // manager will handle it
@@ -544,9 +556,8 b' var IPython = (function (IPython) {'
544 this.code_mirror.setOption('mode', default_mode);
556 this.code_mirror.setOption('mode', default_mode);
545 };
557 };
546
558
559 // Backwards compatibility.
547 IPython.Cell = Cell;
560 IPython.Cell = Cell;
548
561
549 return IPython;
562 return {'Cell': Cell};
550
563 });
551 }(IPython));
552
@@ -1,32 +1,28 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 'base/js/events'
8 //============================================================================
8 ], function(IPython, $, events) {
9 // CellToolbar
10 //============================================================================
11
12
13 /**
14 * A Module to control the per-cell toolbar.
15 * @module IPython
16 * @namespace IPython
17 * @submodule CellToolbar
18 */
19 var IPython = (function (IPython) {
20 "use strict";
9 "use strict";
21
10
22 /**
11 var CellToolbar = function (options) {
23 * @constructor
12 // Constructor
24 * @class CellToolbar
13 //
25 * @param {The cell to attach the metadata UI to} cell
14 // Parameters:
26 */
15 // options: dictionary
27 var CellToolbar = function (cell) {
16 // Dictionary of keyword arguments.
17 // events: $(Events) instance
18 // cell: Cell instance
19 // notebook: Notebook instance
20 //
21 // TODO: This leaks, when cell are deleted
22 // There is still a reference to each celltoolbars.
28 CellToolbar._instances.push(this);
23 CellToolbar._instances.push(this);
29 this.cell = cell;
24 this.notebook = options.notebook;
25 this.cell = options.cell;
30 this.create_element();
26 this.create_element();
31 this.rebuild();
27 this.rebuild();
32 return this;
28 return this;
@@ -34,7 +30,7 b' var IPython = (function (IPython) {'
34
30
35
31
36 CellToolbar.prototype.create_element = function () {
32 CellToolbar.prototype.create_element = function () {
37 this.inner_element = $('<div/>').addClass('celltoolbar')
33 this.inner_element = $('<div/>').addClass('celltoolbar');
38 this.element = $('<div/>').addClass('ctb_hideshow')
34 this.element = $('<div/>').addClass('ctb_hideshow')
39 .append(this.inner_element);
35 .append(this.inner_element);
40 };
36 };
@@ -182,13 +178,14 b' var IPython = (function (IPython) {'
182 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
178 * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5'])
183 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
179 * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5'])
184 */
180 */
185 CellToolbar.register_preset = function(name, preset_list) {
181 CellToolbar.register_preset = function(name, preset_list, notebook) {
186 CellToolbar._presets[name] = preset_list;
182 CellToolbar._presets[name] = preset_list;
187 $([IPython.events]).trigger('preset_added.CellToolbar', {name: name});
183 events.trigger('preset_added.CellToolbar', {name: name});
188 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
184 // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded.
189 // In that case, activate the preset if needed.
185 // In that case, activate the preset if needed.
190 if (IPython.notebook && IPython.notebook.metadata && IPython.notebook.metadata.celltoolbar === name)
186 if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){
191 this.activate_preset(name);
187 CellToolbar.activate_preset(name);
188 }
192 };
189 };
193
190
194
191
@@ -229,7 +226,7 b' var IPython = (function (IPython) {'
229 CellToolbar.rebuild_all();
226 CellToolbar.rebuild_all();
230 }
227 }
231
228
232 $([IPython.events]).trigger('preset_activated.CellToolbar', {name: preset_name});
229 events.trigger('preset_activated.CellToolbar', {name: preset_name});
233 };
230 };
234
231
235
232
@@ -283,7 +280,7 b' var IPython = (function (IPython) {'
283 }
280 }
284
281
285 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
282 // If there are no controls or the cell is a rendered TextCell hide the toolbar.
286 if (!this.ui_controls_list.length || (this.cell instanceof IPython.TextCell && this.cell.rendered)) {
283 if (!this.ui_controls_list.length || (this.cell.cell_type != 'code' && this.cell.rendered)) {
287 this.hide();
284 this.hide();
288 } else {
285 } else {
289 this.show();
286 this.show();
@@ -347,7 +344,7 b' var IPython = (function (IPython) {'
347 setter(cell, !v);
344 setter(cell, !v);
348 chkb.attr("checked", !v);
345 chkb.attr("checked", !v);
349 });
346 });
350 button_container.append($('<div/>').append(lbl));
347 button_container.append($('<span/>').append(lbl));
351 };
348 };
352 };
349 };
353
350
@@ -411,12 +408,12 b' var IPython = (function (IPython) {'
411 select.change(function(){
408 select.change(function(){
412 setter(cell, select.val());
409 setter(cell, select.val());
413 });
410 });
414 button_container.append($('<div/>').append(lbl).append(select));
411 button_container.append($('<span/>').append(lbl).append(select));
415 };
412 };
416 };
413 };
417
414
418
415 // Backwards compatability.
419 IPython.CellToolbar = CellToolbar;
416 IPython.CellToolbar = CellToolbar;
420
417
421 return IPython;
418 return {'CellToolbar': CellToolbar};
422 }(IPython));
419 });
@@ -1,31 +1,29 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'jquery',
6 //----------------------------------------------------------------------------
6 'notebook/js/celltoolbar',
7
7 'base/js/dialog',
8 //============================================================================
8 ], function($, celltoolbar, dialog) {
9 // CellToolbar Default
10 //============================================================================
11
12 /**
13 * Example Use for the CellToolbar library
14 */
15 // IIFE without asignement, we don't modifiy the IPython namespace
16 (function (IPython) {
17 "use strict";
9 "use strict";
18
10
19 var CellToolbar = IPython.CellToolbar;
11 var CellToolbar = celltoolbar.CellToolbar;
20
12
21 var raw_edit = function(cell){
13 var raw_edit = function (cell) {
22 IPython.dialog.edit_metadata(cell.metadata, function (md) {
14 dialog.edit_metadata({
23 cell.metadata = md;
15 md: cell.metadata,
16 callback: function (md) {
17 cell.metadata = md;
18 },
19 name: 'Cell',
20 notebook: this.notebook,
21 keyboard_manager: this.keyboard_manager
24 });
22 });
25 };
23 };
26
24
27 var add_raw_edit_button = function(div, cell) {
25 var add_raw_edit_button = function(div, cell) {
28 var button_container = div;
26 var button_container = $(div);
29 var button = $('<button/>')
27 var button = $('<button/>')
30 .addClass("btn btn-default btn-xs")
28 .addClass("btn btn-default btn-xs")
31 .text("Edit Metadata")
29 .text("Edit Metadata")
@@ -36,11 +34,18 b''
36 button_container.append(button);
34 button_container.append(button);
37 };
35 };
38
36
39 CellToolbar.register_callback('default.rawedit', add_raw_edit_button);
37 var register = function (notebook) {
40 var example_preset = [];
38 CellToolbar.register_callback('default.rawedit', add_raw_edit_button);
41 example_preset.push('default.rawedit');
39 raw_edit = $.proxy(raw_edit, {
40 notebook: notebook,
41 keyboard_manager: notebook.keyboard_manager
42 });
42
43
43 CellToolbar.register_preset('Edit Metadata', example_preset);
44 var example_preset = [];
44 console.log('Default extension for cell metadata editing loaded.');
45 example_preset.push('default.rawedit');
45
46
46 }(IPython));
47 CellToolbar.register_preset('Edit Metadata', example_preset, notebook);
48 console.log('Default extension for cell metadata editing loaded.');
49 };
50 return {'register': register};
51 });
@@ -1,28 +1,20 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 // Example Use for the CellToolbar library
5 // the file COPYING, distributed as part of this software.
5 // add the following to your custom.js to load
6 //----------------------------------------------------------------------------
6 // Celltoolbar UI for slideshow
7
7
8 //============================================================================
8 // ```
9 // CellToolbar Example
9 // $.getScript('/static/js/celltoolbarpresets/example.js');
10 //============================================================================
10 // ```
11
11 define([
12 /**
12 'jquery',
13 * Example Use for the CellToolbar library
13 'notebook/js/celltoolbar',
14 * add the following to your custom.js to load
14 ], function($, celltoolbar) {
15 * Celltoolbar UI for slideshow
16 *
17 * ```
18 * $.getScript('/static/js/celltoolbarpresets/example.js');
19 * ```
20 */
21 // IIFE without asignement, we don't modifiy the IPython namespace
22 (function (IPython) {
23 "use strict";
15 "use strict";
24
16
25 var CellToolbar = IPython.CellToolbar;
17 var CellToolbar = celltoolbar.CellToolbar;
26
18
27 var example_preset = [];
19 var example_preset = [];
28
20
@@ -32,32 +24,32 b''
32 var fun = function(value){
24 var fun = function(value){
33 try{
25 try{
34 if(value){
26 if(value){
35 cell.code_mirror.setOption('readOnly','nocursor')
27 cell.code_mirror.setOption('readOnly','nocursor');
36 button.button('option','icons',{primary:'ui-icon-locked'})
28 button.button('option','icons',{primary:'ui-icon-locked'});
37 } else {
29 } else {
38 cell.code_mirror.setOption('readOnly',false)
30 cell.code_mirror.setOption('readOnly',false);
39 button.button('option','icons',{primary:'ui-icon-unlocked'})
31 button.button('option','icons',{primary:'ui-icon-unlocked'});
40 }
32 }
41 } catch(e){}
33 } catch(e){}
42
34
43 }
35 };
44 fun(cell.metadata.ro)
36 fun(cell.metadata.ro);
45 button.click(function(){
37 button.click(function(){
46 var v = cell.metadata.ro;
38 var v = cell.metadata.ro;
47 var locked = !v;
39 var locked = !v;
48 cell.metadata.ro = locked;
40 cell.metadata.ro = locked;
49 fun(locked)
41 fun(locked);
50 })
42 })
51 .css('height','16px')
43 .css('height','16px')
52 .css('width','35px');
44 .css('width','35px');
53 button_container.append(button);
45 button_container.append(button);
54 }
46 };
55
47
56 CellToolbar.register_callback('example.lock',simple_button);
48 CellToolbar.register_callback('example.lock',simple_button);
57 example_preset.push('example.lock');
49 example_preset.push('example.lock');
58
50
59 var toggle_test = function(div, cell) {
51 var toggle_test = function(div, cell) {
60 var button_container = $(div)
52 var button_container = $(div);
61 var button = $('<div/>')
53 var button = $('<div/>')
62 .button({label:String(cell.metadata.foo)}).
54 .button({label:String(cell.metadata.foo)}).
63 css('width','65px');
55 css('width','65px');
@@ -65,9 +57,9 b''
65 var v = cell.metadata.foo;
57 var v = cell.metadata.foo;
66 cell.metadata.foo = !v;
58 cell.metadata.foo = !v;
67 button.button("option","label",String(!v));
59 button.button("option","label",String(!v));
68 })
60 });
69 button_container.append(button);
61 button_container.append(button);
70 }
62 };
71
63
72 CellToolbar.register_callback('example.toggle',toggle_test);
64 CellToolbar.register_callback('example.toggle',toggle_test);
73 example_preset.push('example.toggle');
65 example_preset.push('example.toggle');
@@ -76,16 +68,16 b''
76 // setter
68 // setter
77 function(cell, value){
69 function(cell, value){
78 // we check that the slideshow namespace exist and create it if needed
70 // we check that the slideshow namespace exist and create it if needed
79 if (cell.metadata.yn_test == undefined){cell.metadata.yn_test = {}}
71 if (cell.metadata.yn_test === undefined){cell.metadata.yn_test = {};}
80 // set the value
72 // set the value
81 cell.metadata.yn_test.value = value
73 cell.metadata.yn_test.value = value;
82 },
74 },
83 //geter
75 //geter
84 function(cell){ var ns = cell.metadata.yn_test;
76 function(cell){ var ns = cell.metadata.yn_test;
85 // if the slideshow namespace does not exist return `undefined`
77 // if the slideshow namespace does not exist return `undefined`
86 // (will be interpreted as `false` by checkbox) otherwise
78 // (will be interpreted as `false` by checkbox) otherwise
87 // return the value
79 // return the value
88 return (ns == undefined)? undefined: ns.value
80 return (ns === undefined)? undefined: ns.value;
89 }
81 }
90 );
82 );
91
83
@@ -103,16 +95,16 b''
103 // setter
95 // setter
104 function(cell,value){
96 function(cell,value){
105 // we check that the slideshow namespace exist and create it if needed
97 // we check that the slideshow namespace exist and create it if needed
106 if (cell.metadata.test == undefined){cell.metadata.test = {}}
98 if (cell.metadata.test === undefined){cell.metadata.test = {};}
107 // set the value
99 // set the value
108 cell.metadata.test.slide_type = value
100 cell.metadata.test.slide_type = value;
109 },
101 },
110 //geter
102 //geter
111 function(cell){ var ns = cell.metadata.test;
103 function(cell){ var ns = cell.metadata.test;
112 // if the slideshow namespace does not exist return `undefined`
104 // if the slideshow namespace does not exist return `undefined`
113 // (will be interpreted as `false` by checkbox) otherwise
105 // (will be interpreted as `false` by checkbox) otherwise
114 // return the value
106 // return the value
115 return (ns == undefined)? undefined: ns.slide_type
107 return (ns === undefined)? undefined: ns.slide_type;
116 });
108 });
117
109
118 CellToolbar.register_callback('example.select',select_test);
110 CellToolbar.register_callback('example.select',select_test);
@@ -120,7 +112,7 b''
120
112
121 var simple_dialog = function(title,text){
113 var simple_dialog = function(title,text){
122 var dlg = $('<div/>').attr('title',title)
114 var dlg = $('<div/>').attr('title',title)
123 .append($('<p/>').text(text))
115 .append($('<p/>').text(text));
124 $(dlg).dialog({
116 $(dlg).dialog({
125 autoOpen: true,
117 autoOpen: true,
126 height: 300,
118 height: 300,
@@ -131,24 +123,26 b''
131 $(this).remove();
123 $(this).remove();
132 }
124 }
133 });
125 });
134 }
126 };
135
127
136 var add_simple_dialog_button = function(div, cell) {
128 var add_simple_dialog_button = function(div, cell) {
137 var help_text = ["This is the Metadata editting UI.",
129 var help_text = ["This is the Metadata editting UI.",
138 "It heavily rely on plugin to work ",
130 "It heavily rely on plugin to work ",
139 "and is still under developpement. You shouldn't wait too long before",
131 "and is still under developpement. You shouldn't wait too long before",
140 " seeing some customisable buttons in those toolbar."
132 " seeing some customisable buttons in those toolbar."
141 ].join('\n')
133 ].join('\n');
142 var button_container = $(div)
134 var button_container = $(div);
143 var button = $('<div/>').button({label:'?'})
135 var button = $('<div/>').button({label:'?'})
144 .click(function(){simple_dialog('help',help_text); return false;})
136 .click(function(){simple_dialog('help',help_text); return false;});
145 button_container.append(button);
137 button_container.append(button);
146 }
138 };
147
148 CellToolbar.register_callback('example.help',add_simple_dialog_button)
149 example_preset.push('example.help')
150
139
151 CellToolbar.register_preset('Example',example_preset);
140 var register = function (notebook) {
152 console.log('Example extension for metadata editing loaded.');
141 CellToolbar.register_callback('example.help',add_simple_dialog_button);
142 example_preset.push('example.help');
153
143
154 }(IPython));
144 CellToolbar.register_preset('Example',example_preset, notebook);
145 console.log('Example extension for metadata editing loaded.');
146 };
147 return {'register': register};
148 });
@@ -1,18 +1,15 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // CellToolbar Example
5 'jquery',
10 //============================================================================
6 'notebook/js/celltoolbar',
7 'base/js/dialog',
8 'base/js/keyboard',
9 ], function($, celltoolbar, dialog, keyboard) {
10 "use strict";
11
11
12 (function(IPython) {
12 var CellToolbar = celltoolbar.CellToolbar;
13 "use strict";
14
15 var CellToolbar = IPython.CellToolbar;
16 var raw_cell_preset = [];
13 var raw_cell_preset = [];
17
14
18 var select_type = CellToolbar.utils.select_ui_generator([
15 var select_type = CellToolbar.utils.select_ui_generator([
@@ -39,7 +36,7 b''
39 $('<input/>').attr('type','text').attr('size','25')
36 $('<input/>').attr('type','text').attr('size','25')
40 .val(cell.metadata.raw_mimetype || "-")
37 .val(cell.metadata.raw_mimetype || "-")
41 );
38 );
42 IPython.dialog.modal({
39 dialog.modal({
43 title: "Raw Cell MIME Type",
40 title: "Raw Cell MIME Type",
44 body: dialog,
41 body: dialog,
45 buttons : {
42 buttons : {
@@ -57,7 +54,7 b''
57 var that = $(this);
54 var that = $(this);
58 // Upon ENTER, click the OK button.
55 // Upon ENTER, click the OK button.
59 that.find('input[type="text"]').keydown(function (event, ui) {
56 that.find('input[type="text"]').keydown(function (event, ui) {
60 if (event.which === IPython.keyboard.keycodes.enter) {
57 if (event.which === keyboard.keycodes.enter) {
61 that.find('.btn-primary').first().click();
58 that.find('.btn-primary').first().click();
62 return false;
59 return false;
63 }
60 }
@@ -77,11 +74,13 b''
77 "Raw NBConvert Format"
74 "Raw NBConvert Format"
78 );
75 );
79
76
80 CellToolbar.register_callback('raw_cell.select', select_type, ['raw']);
77 var register = function (notebook) {
81
78 CellToolbar.register_callback('raw_cell.select', select_type, ['raw']);
82 raw_cell_preset.push('raw_cell.select');
79 raw_cell_preset.push('raw_cell.select');
83
80
84 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset);
81 CellToolbar.register_preset('Raw Cell Format', raw_cell_preset, notebook);
85 console.log('Raw Cell Format toolbar preset loaded.');
82 console.log('Raw Cell Format toolbar preset loaded.');
83 };
84 return {'register': register};
86
85
87 }(IPython));
86 });
@@ -1,19 +1,14 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 //CellToolbar Example
5 'jquery',
10 //============================================================================
6 'notebook/js/celltoolbar',
11
7 ], function($, celltoolbar) {
12 // IIFE without asignement, we don't modifiy the IPython namespace
13 (function (IPython) {
14 "use strict";
8 "use strict";
15
9
16 var CellToolbar = IPython.CellToolbar;
10
11 var CellToolbar = celltoolbar.CellToolbar;
17 var slideshow_preset = [];
12 var slideshow_preset = [];
18
13
19 var select_type = CellToolbar.utils.select_ui_generator([
14 var select_type = CellToolbar.utils.select_ui_generator([
@@ -27,24 +22,25 b''
27 // setter
22 // setter
28 function(cell, value){
23 function(cell, value){
29 // we check that the slideshow namespace exist and create it if needed
24 // we check that the slideshow namespace exist and create it if needed
30 if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}}
25 if (cell.metadata.slideshow === undefined){cell.metadata.slideshow = {};}
31 // set the value
26 // set the value
32 cell.metadata.slideshow.slide_type = value
27 cell.metadata.slideshow.slide_type = value;
33 },
28 },
34 //geter
29 //geter
35 function(cell){ var ns = cell.metadata.slideshow;
30 function(cell){ var ns = cell.metadata.slideshow;
36 // if the slideshow namespace does not exist return `undefined`
31 // if the slideshow namespace does not exist return `undefined`
37 // (will be interpreted as `false` by checkbox) otherwise
32 // (will be interpreted as `false` by checkbox) otherwise
38 // return the value
33 // return the value
39 return (ns == undefined)? undefined: ns.slide_type
34 return (ns === undefined)? undefined: ns.slide_type;
40 },
35 },
41 "Slide Type");
36 "Slide Type");
42
37
43 CellToolbar.register_callback('slideshow.select',select_type);
38 var register = function (notebook) {
44
39 CellToolbar.register_callback('slideshow.select',select_type);
45 slideshow_preset.push('slideshow.select');
40 slideshow_preset.push('slideshow.select');
46
47 CellToolbar.register_preset('Slideshow',slideshow_preset);
48 console.log('Slideshow extension for metadata editing loaded.');
49
41
50 }(IPython));
42 CellToolbar.register_preset('Slideshow',slideshow_preset, notebook);
43 console.log('Slideshow extension for metadata editing loaded.');
44 };
45 return {'register': register};
46 });
@@ -1,68 +1,67 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 'base/js/utils',
8 //============================================================================
8 'base/js/keyboard',
9 // CodeCell
9 'notebook/js/cell',
10 //============================================================================
10 'notebook/js/outputarea',
11 /**
11 'notebook/js/completer',
12 * An extendable module that provide base functionnality to create cell for notebook.
12 'notebook/js/celltoolbar',
13 * @module IPython
13 ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) {
14 * @namespace IPython
15 * @submodule CodeCell
16 */
17
18
19 /* local util for codemirror */
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21
22 /**
23 *
24 * function to delete until previous non blanking space character
25 * or first multiple of 4 tabstop.
26 * @private
27 */
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 var tabsize = cm.getOption('tabSize');
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 var select = cm.getRange(from,cur);
36 if( select.match(/^\ +$/) !== null){
37 cm.replaceRange("",from,cur);
38 } else {
39 cm.deleteH(-1,"char");
40 }
41 };
42
43
44 var IPython = (function (IPython) {
45 "use strict";
14 "use strict";
15 var Cell = cell.Cell;
46
16
47 var utils = IPython.utils;
17 /* local util for codemirror */
48 var keycodes = IPython.keyboard.keycodes;
18 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
49
19
50 /**
20 /**
51 * A Cell conceived to write code.
52 *
21 *
53 * The kernel doesn't have to be set at creation time, in that case
22 * function to delete until previous non blanking space character
54 * it will be null and set_kernel has to be called later.
23 * or first multiple of 4 tabstop.
55 * @class CodeCell
24 * @private
56 * @extends IPython.Cell
57 *
58 * @constructor
59 * @param {Object|null} kernel
60 * @param {object|undefined} [options]
61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 */
25 */
26 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
27 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
28 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
29 var cur = cm.getCursor(), line = cm.getLine(cur.line);
30 var tabsize = cm.getOption('tabSize');
31 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
32 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
33 var select = cm.getRange(from,cur);
34 if( select.match(/^\ +$/) !== null){
35 cm.replaceRange("",from,cur);
36 } else {
37 cm.deleteH(-1,"char");
38 }
39 };
40
41 var keycodes = keyboard.keycodes;
42
63 var CodeCell = function (kernel, options) {
43 var CodeCell = function (kernel, options) {
44 // Constructor
45 //
46 // A Cell conceived to write code.
47 //
48 // Parameters:
49 // kernel: Kernel instance
50 // The kernel doesn't have to be set at creation time, in that case
51 // it will be null and set_kernel has to be called later.
52 // options: dictionary
53 // Dictionary of keyword arguments.
54 // events: $(Events) instance
55 // config: dictionary
56 // keyboard_manager: KeyboardManager instance
57 // notebook: Notebook instance
58 // tooltip: Tooltip instance
64 this.kernel = kernel || null;
59 this.kernel = kernel || null;
60 this.notebook = options.notebook;
65 this.collapsed = false;
61 this.collapsed = false;
62 this.events = options.events;
63 this.tooltip = options.tooltip;
64 this.config = options.config;
66
65
67 // create all attributed in constructor function
66 // create all attributed in constructor function
68 // even if null for V8 VM optimisation
67 // even if null for V8 VM optimisation
@@ -77,9 +76,11 b' var IPython = (function (IPython) {'
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
76 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 };
77 };
79
78
80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
79 var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options});
81
80 Cell.apply(this,[{
82 IPython.Cell.apply(this,[options]);
81 config: config,
82 keyboard_manager: options.keyboard_manager,
83 events: this.events}]);
83
84
84 // Attributes we want to override in this subclass.
85 // Attributes we want to override in this subclass.
85 this.cell_type = "code";
86 this.cell_type = "code";
@@ -109,29 +110,31 b' var IPython = (function (IPython) {'
109
110
110 CodeCell.msg_cells = {};
111 CodeCell.msg_cells = {};
111
112
112 CodeCell.prototype = new IPython.Cell();
113 CodeCell.prototype = new Cell();
113
114
114 /**
115 /**
115 * @method auto_highlight
116 * @method auto_highlight
116 */
117 */
117 CodeCell.prototype.auto_highlight = function () {
118 CodeCell.prototype.auto_highlight = function () {
118 this._auto_highlight(IPython.config.cell_magic_highlight);
119 this._auto_highlight(this.config.cell_magic_highlight);
119 };
120 };
120
121
121 /** @method create_element */
122 /** @method create_element */
122 CodeCell.prototype.create_element = function () {
123 CodeCell.prototype.create_element = function () {
123 IPython.Cell.prototype.create_element.apply(this, arguments);
124 Cell.prototype.create_element.apply(this, arguments);
124
125
125 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
126 var cell = $('<div></div>').addClass('cell code_cell');
126 cell.attr('tabindex','2');
127 cell.attr('tabindex','2');
127
128
128 var input = $('<div></div>').addClass('input');
129 var input = $('<div></div>').addClass('input');
129 var prompt = $('<div/>').addClass('prompt input_prompt');
130 var prompt = $('<div/>').addClass('prompt input_prompt');
130 var inner_cell = $('<div/>').addClass('inner_cell');
131 var inner_cell = $('<div/>').addClass('inner_cell');
131 this.celltoolbar = new IPython.CellToolbar(this);
132 this.celltoolbar = new celltoolbar.CellToolbar({
133 cell: this,
134 notebook: this.notebook});
132 inner_cell.append(this.celltoolbar.element);
135 inner_cell.append(this.celltoolbar.element);
133 var input_area = $('<div/>').addClass('input_area');
136 var input_area = $('<div/>').addClass('input_area');
134 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
137 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
135 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
138 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
136 inner_cell.append(input_area);
139 inner_cell.append(input_area);
137 input.append(prompt).append(inner_cell);
140 input.append(prompt).append(inner_cell);
@@ -158,13 +161,17 b' var IPython = (function (IPython) {'
158 var output = $('<div></div>');
161 var output = $('<div></div>');
159 cell.append(input).append(widget_area).append(output);
162 cell.append(input).append(widget_area).append(output);
160 this.element = cell;
163 this.element = cell;
161 this.output_area = new IPython.OutputArea(output, true);
164 this.output_area = new outputarea.OutputArea({
162 this.completer = new IPython.Completer(this);
165 selector: output,
166 prompt_area: true,
167 events: this.events,
168 keyboard_manager: this.keyboard_manager});
169 this.completer = new completer.Completer(this, this.events);
163 };
170 };
164
171
165 /** @method bind_events */
172 /** @method bind_events */
166 CodeCell.prototype.bind_events = function () {
173 CodeCell.prototype.bind_events = function () {
167 IPython.Cell.prototype.bind_events.apply(this);
174 Cell.prototype.bind_events.apply(this);
168 var that = this;
175 var that = this;
169
176
170 this.element.focusout(
177 this.element.focusout(
@@ -187,7 +194,7 b' var IPython = (function (IPython) {'
187 // they are sent, and remove tooltip if any, except for tab again
194 // they are sent, and remove tooltip if any, except for tab again
188 var tooltip_closed = null;
195 var tooltip_closed = null;
189 if (event.type === 'keydown' && event.which != keycodes.tab ) {
196 if (event.type === 'keydown' && event.which != keycodes.tab ) {
190 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
197 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
191 }
198 }
192
199
193 var cur = editor.getCursor();
200 var cur = editor.getCursor();
@@ -195,21 +202,21 b' var IPython = (function (IPython) {'
195 this.auto_highlight();
202 this.auto_highlight();
196 }
203 }
197
204
198 if (event.which === keycodes.down && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
205 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
199 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
206 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
200 // browser and keyboard layout !
207 // browser and keyboard layout !
201 // Pressing '(' , request tooltip, don't forget to reappend it
208 // Pressing '(' , request tooltip, don't forget to reappend it
202 // The second argument says to hide the tooltip if the docstring
209 // The second argument says to hide the tooltip if the docstring
203 // is actually empty
210 // is actually empty
204 IPython.tooltip.pending(that, true);
211 this.tooltip.pending(that, true);
205 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
212 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
206 // If tooltip is active, cancel it. The call to
213 // If tooltip is active, cancel it. The call to
207 // remove_and_cancel_tooltip above doesn't pass, force=true.
214 // remove_and_cancel_tooltip above doesn't pass, force=true.
208 // Because of this it won't actually close the tooltip
215 // Because of this it won't actually close the tooltip
209 // if it is in sticky mode. Thus, we have to check again if it is open
216 // if it is in sticky mode. Thus, we have to check again if it is open
210 // and close it with force=true.
217 // and close it with force=true.
211 if (!IPython.tooltip._hidden) {
218 if (!this.tooltip._hidden) {
212 IPython.tooltip.remove_and_cancel_tooltip(true);
219 this.tooltip.remove_and_cancel_tooltip(true);
213 }
220 }
214 // If we closed the tooltip, don't let CM or the global handlers
221 // If we closed the tooltip, don't let CM or the global handlers
215 // handle this event.
222 // handle this event.
@@ -223,12 +230,12 b' var IPython = (function (IPython) {'
223 return false;
230 return false;
224 }
231 }
225 }
232 }
226 IPython.tooltip.request(that);
233 this.tooltip.request(that);
227 event.stop();
234 event.stop();
228 return true;
235 return true;
229 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
236 } else if (event.keyCode === keycodes.tab && event.type == 'keydown') {
230 // Tab completion.
237 // Tab completion.
231 IPython.tooltip.remove_and_cancel_tooltip();
238 this.tooltip.remove_and_cancel_tooltip();
232 if (editor.somethingSelected()) {
239 if (editor.somethingSelected()) {
233 return false;
240 return false;
234 }
241 }
@@ -246,7 +253,7 b' var IPython = (function (IPython) {'
246
253
247 // keyboard event wasn't one of those unique to code cells, let's see
254 // keyboard event wasn't one of those unique to code cells, let's see
248 // if it's one of the generic ones (i.e. check edit mode shortcuts)
255 // if it's one of the generic ones (i.e. check edit mode shortcuts)
249 return IPython.Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
256 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
250 };
257 };
251
258
252 // Kernel related calls.
259 // Kernel related calls.
@@ -305,7 +312,7 b' var IPython = (function (IPython) {'
305 };
312 };
306
313
307 CodeCell.prototype._open_with_pager = function (payload) {
314 CodeCell.prototype._open_with_pager = function (payload) {
308 $([IPython.events]).trigger('open_with_text.Pager', payload);
315 this.events.trigger('open_with_text.Pager', payload);
309 };
316 };
310
317
311 /**
318 /**
@@ -315,7 +322,7 b' var IPython = (function (IPython) {'
315 CodeCell.prototype._handle_execute_reply = function (msg) {
322 CodeCell.prototype._handle_execute_reply = function (msg) {
316 this.set_input_prompt(msg.content.execution_count);
323 this.set_input_prompt(msg.content.execution_count);
317 this.element.removeClass("running");
324 this.element.removeClass("running");
318 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
325 this.events.trigger('set_dirty.Notebook', {value: true});
319 };
326 };
320
327
321 /**
328 /**
@@ -324,7 +331,7 b' var IPython = (function (IPython) {'
324 */
331 */
325 CodeCell.prototype._handle_set_next_input = function (payload) {
332 CodeCell.prototype._handle_set_next_input = function (payload) {
326 var data = {'cell': this, 'text': payload.text};
333 var data = {'cell': this, 'text': payload.text};
327 $([IPython.events]).trigger('set_next_input.Notebook', data);
334 this.events.trigger('set_next_input.Notebook', data);
328 };
335 };
329
336
330 /**
337 /**
@@ -339,7 +346,7 b' var IPython = (function (IPython) {'
339 // Basic cell manipulation.
346 // Basic cell manipulation.
340
347
341 CodeCell.prototype.select = function () {
348 CodeCell.prototype.select = function () {
342 var cont = IPython.Cell.prototype.select.apply(this);
349 var cont = Cell.prototype.select.apply(this);
343 if (cont) {
350 if (cont) {
344 this.code_mirror.refresh();
351 this.code_mirror.refresh();
345 this.auto_highlight();
352 this.auto_highlight();
@@ -348,7 +355,7 b' var IPython = (function (IPython) {'
348 };
355 };
349
356
350 CodeCell.prototype.render = function () {
357 CodeCell.prototype.render = function () {
351 var cont = IPython.Cell.prototype.render.apply(this);
358 var cont = Cell.prototype.render.apply(this);
352 // Always execute, even if we are already in the rendered state
359 // Always execute, even if we are already in the rendered state
353 return cont;
360 return cont;
354 };
361 };
@@ -451,7 +458,7 b' var IPython = (function (IPython) {'
451 // JSON serialization
458 // JSON serialization
452
459
453 CodeCell.prototype.fromJSON = function (data) {
460 CodeCell.prototype.fromJSON = function (data) {
454 IPython.Cell.prototype.fromJSON.apply(this, arguments);
461 Cell.prototype.fromJSON.apply(this, arguments);
455 if (data.cell_type === 'code') {
462 if (data.cell_type === 'code') {
456 if (data.input !== undefined) {
463 if (data.input !== undefined) {
457 this.set_text(data.input);
464 this.set_text(data.input);
@@ -479,7 +486,7 b' var IPython = (function (IPython) {'
479
486
480
487
481 CodeCell.prototype.toJSON = function () {
488 CodeCell.prototype.toJSON = function () {
482 var data = IPython.Cell.prototype.toJSON.apply(this);
489 var data = Cell.prototype.toJSON.apply(this);
483 data.input = this.get_text();
490 data.input = this.get_text();
484 // is finite protect against undefined and '*' value
491 // is finite protect against undefined and '*' value
485 if (isFinite(this.input_prompt_number)) {
492 if (isFinite(this.input_prompt_number)) {
@@ -499,11 +506,11 b' var IPython = (function (IPython) {'
499 * @return is the action being taken
506 * @return is the action being taken
500 */
507 */
501 CodeCell.prototype.unselect = function () {
508 CodeCell.prototype.unselect = function () {
502 var cont = IPython.Cell.prototype.unselect.apply(this);
509 var cont = Cell.prototype.unselect.apply(this);
503 if (cont) {
510 if (cont) {
504 // When a code cell is usnelected, make sure that the corresponding
511 // When a code cell is usnelected, make sure that the corresponding
505 // tooltip and completer to that cell is closed.
512 // tooltip and completer to that cell is closed.
506 IPython.tooltip.remove_and_cancel_tooltip(true);
513 this.tooltip.remove_and_cancel_tooltip(true);
507 if (this.completer !== null) {
514 if (this.completer !== null) {
508 this.completer.close();
515 this.completer.close();
509 }
516 }
@@ -511,7 +518,8 b' var IPython = (function (IPython) {'
511 return cont;
518 return cont;
512 };
519 };
513
520
521 // Backwards compatability.
514 IPython.CodeCell = CodeCell;
522 IPython.CodeCell = CodeCell;
515
523
516 return IPython;
524 return {'CodeCell': CodeCell};
517 }(IPython));
525 });
@@ -7,10 +7,15 b" CodeMirror.requireMode('python',function(){"
7 "use strict";
7 "use strict";
8
8
9 CodeMirror.defineMode("ipython", function(conf, parserConf) {
9 CodeMirror.defineMode("ipython", function(conf, parserConf) {
10
10 var pythonConf = {};
11 parserConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
11 for (var prop in parserConf) {
12 parserConf.name = 'python'
12 if (parserConf.hasOwnProperty(prop)) {
13 return CodeMirror.getMode(conf, parserConf);
13 pythonConf[prop] = parserConf[prop];
14 }
15 }
16 pythonConf.name = 'python';
17 pythonConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
18 return CodeMirror.getMode(conf, pythonConf);
14 }, 'python');
19 }, 'python');
15
20
16 CodeMirror.defineMIME("text/x-ipython", "ipython");
21 CodeMirror.defineMIME("text/x-ipython", "ipython");
@@ -8,7 +8,6 b''
8
8
9 CodeMirror.requireMode('gfm', function(){
9 CodeMirror.requireMode('gfm', function(){
10 CodeMirror.requireMode('stex', function(){
10 CodeMirror.requireMode('stex', function(){
11 console.log('defining custom mode...');
12 CodeMirror.defineMode("ipythongfm", function(config, parserConfig) {
11 CodeMirror.defineMode("ipythongfm", function(config, parserConfig) {
13
12
14 var gfm_mode = CodeMirror.getMode(config, "gfm");
13 var gfm_mode = CodeMirror.getMode(config, "gfm");
@@ -1,17 +1,17 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 // Completer
4 define([
5 //
5 'base/js/namespace',
6 // Completer is be a class that takes a cell instance.
6 'jquery',
7
7 'base/js/utils',
8 var IPython = (function (IPython) {
8 'base/js/keyboard',
9 // that will prevent us from misspelling
9 'notebook/js/contexthint',
10 ], function(IPython, $, utils, keyboard) {
10 "use strict";
11 "use strict";
11
12
12 // easier key mapping
13 // easier key mapping
13 var keycodes = IPython.keyboard.keycodes;
14 var keycodes = keyboard.keycodes;
14 var utils = IPython.utils;
15
15
16 var prepend_n_prc = function(str, n) {
16 var prepend_n_prc = function(str, n) {
17 for( var i =0 ; i< n ; i++){
17 for( var i =0 ; i< n ; i++){
@@ -78,14 +78,14 b' var IPython = (function (IPython) {'
78 }
78 }
79
79
80
80
81 var Completer = function (cell) {
81 var Completer = function (cell, events) {
82 this.cell = cell;
82 this.cell = cell;
83 this.editor = cell.code_mirror;
83 this.editor = cell.code_mirror;
84 var that = this;
84 var that = this;
85 $([IPython.events]).on('status_busy.Kernel', function () {
85 events.on('status_busy.Kernel', function () {
86 that.skip_kernel_completion = true;
86 that.skip_kernel_completion = true;
87 });
87 });
88 $([IPython.events]).on('status_idle.Kernel', function () {
88 events.on('status_idle.Kernel', function () {
89 that.skip_kernel_completion = false;
89 that.skip_kernel_completion = false;
90 });
90 });
91 };
91 };
@@ -351,6 +351,18 b' var IPython = (function (IPython) {'
351 }
351 }
352 index = Math.min(Math.max(index, 0), options.length-1);
352 index = Math.min(Math.max(index, 0), options.length-1);
353 this.sel[0].selectedIndex = index;
353 this.sel[0].selectedIndex = index;
354 } else if (code == keycodes.pageup || code == keycodes.pagedown) {
355 CodeMirror.e_stop(event);
356
357 var options = this.sel.find('option');
358 var index = this.sel[0].selectedIndex;
359 if (code == keycodes.pageup) {
360 index -= 10; // As 10 is the hard coded size of the drop down menu
361 } else {
362 index += 10;
363 }
364 index = Math.min(Math.max(index, 0), options.length-1);
365 this.sel[0].selectedIndex = index;
354 } else if (code == keycodes.left || code == keycodes.right) {
366 } else if (code == keycodes.left || code == keycodes.right) {
355 this.close();
367 this.close();
356 }
368 }
@@ -379,7 +391,9 b' var IPython = (function (IPython) {'
379 that.carry_on_completion();
391 that.carry_on_completion();
380 }, 50);
392 }, 50);
381 };
393 };
394
395 // For backwards compatability.
382 IPython.Completer = Completer;
396 IPython.Completer = Completer;
383
397
384 return IPython;
398 return {'Completer': Completer};
385 }(IPython));
399 });
@@ -1,28 +1,9 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([], function() {
9 // Notebook
10 //============================================================================
11
12 /**
13 * @module IPython
14 * @namespace IPython
15 **/
16
17 var IPython = (function (IPython) {
18 "use strict";
5 "use strict";
19 /**
6
20 * A place where some stuff can be confugured.
21 *
22 * @class config
23 * @static
24 *
25 **/
26 var default_config = {
7 var default_config = {
27 /**
8 /**
28 * Dictionary of object to autodetect highlight mode for code cell.
9 * Dictionary of object to autodetect highlight mode for code cell.
@@ -50,30 +31,25 b' var IPython = (function (IPython) {'
50 * cell_magic_highlight['javascript'] = {'reg':[/^var/]}
31 * cell_magic_highlight['javascript'] = {'reg':[/^var/]}
51 */
32 */
52 cell_magic_highlight : {
33 cell_magic_highlight : {
53 'magic_javascript' :{'reg':[/^%%javascript/]}
34 'magic_javascript' :{'reg':[/^%%javascript/]},
54 ,'magic_perl' :{'reg':[/^%%perl/]}
35 'magic_perl' :{'reg':[/^%%perl/]},
55 ,'magic_ruby' :{'reg':[/^%%ruby/]}
36 'magic_ruby' :{'reg':[/^%%ruby/]},
56 ,'magic_python' :{'reg':[/^%%python3?/]}
37 'magic_python' :{'reg':[/^%%python3?/]},
57 ,'magic_shell' :{'reg':[/^%%bash/]}
38 'magic_shell' :{'reg':[/^%%bash/]},
58 ,'magic_r' :{'reg':[/^%%R/]}
39 'magic_r' :{'reg':[/^%%R/]},
59 ,'magic_text/x-cython' :{'reg':[/^%%cython/]}
40 'magic_text/x-cython' :{'reg':[/^%%cython/]},
60 },
41 },
61
42
62 /**
43 /**
63 * same as `cell_magic_highlight` but for raw cells
44 * same as `cell_magic_highlight` but for raw cells
64 * @attribute raw_cell_highlight
45 * @attribute raw_cell_highlight
65 */
46 */
66 raw_cell_highlight : {
47 raw_cell_highlight : {
67 'diff' :{'reg':[/^diff/]}
48 'diff' :{'reg':[/^diff/]}
68 },
49 },
69
50 };
70 };
51
71
52 return {
72 // use the same method to merge user configuration
53 'default_config': default_config,
73 IPython.config = {};
54 };
74 $.extend(IPython.config, default_config);
55 });
75
76 return IPython;
77
78 }(IPython));
79
@@ -1,12 +1,15 b''
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
1 // highly adapted for codemiror jshint
4 // highly adapted for codemiror jshint
2 (function () {
5 define([], function() {
3 "use strict";
6 "use strict";
4
7
5 function forEach(arr, f) {
8 var forEach = function(arr, f) {
6 for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
9 for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
7 }
10 };
8
11
9 function arrayContains(arr, item) {
12 var arrayContains = function(arr, item) {
10 if (!Array.prototype.indexOf) {
13 if (!Array.prototype.indexOf) {
11 var i = arr.length;
14 var i = arr.length;
12 while (i--) {
15 while (i--) {
@@ -17,7 +20,7 b''
17 return false;
20 return false;
18 }
21 }
19 return arr.indexOf(item) != -1;
22 return arr.indexOf(item) != -1;
20 }
23 };
21
24
22 CodeMirror.contextHint = function (editor) {
25 CodeMirror.contextHint = function (editor) {
23 // Find the token at the cursor
26 // Find the token at the cursor
@@ -26,7 +29,7 b''
26 tprop = token;
29 tprop = token;
27 // If it's not a 'word-style' token, ignore the token.
30 // If it's not a 'word-style' token, ignore the token.
28 // If it is a property, find out what it is a property of.
31 // If it is a property, find out what it is a property of.
29 var list = new Array();
32 var list = [];
30 var clist = getCompletions(token, editor);
33 var clist = getCompletions(token, editor);
31 for (var i = 0; i < clist.length; i++) {
34 for (var i = 0; i < clist.length; i++) {
32 list.push({
35 list.push({
@@ -40,55 +43,56 b''
40 line: cur.line,
43 line: cur.line,
41 ch: token.end
44 ch: token.end
42 }
45 }
43 })
46 });
44 }
47 }
45 return list;
48 return list;
46 }
49 };
47
50
48 // find all 'words' of current cell
51 // find all 'words' of current cell
49 var getAllTokens = function (editor) {
52 var getAllTokens = function (editor) {
50 var found = [];
53 var found = [];
51
54
52 // add to found if not already in it
55 // add to found if not already in it
53
56
54
57
55 function maybeAdd(str) {
58 function maybeAdd(str) {
56 if (!arrayContains(found, str)) found.push(str);
59 if (!arrayContains(found, str)) found.push(str);
57 }
60 }
58
61
59 // loop through all token on all lines
62 // loop through all token on all lines
60 var lineCount = editor.lineCount();
63 var lineCount = editor.lineCount();
61 // loop on line
64 // loop on line
62 for (var l = 0; l < lineCount; l++) {
65 for (var l = 0; l < lineCount; l++) {
63 var line = editor.getLine(l);
66 var line = editor.getLine(l);
64 //loop on char
67 //loop on char
65 for (var c = 1; c < line.length; c++) {
68 for (var c = 1; c < line.length; c++) {
66 var tk = editor.getTokenAt({
69 var tk = editor.getTokenAt({
67 line: l,
70 line: l,
68 ch: c
71 ch: c
69 });
72 });
70 // if token has a class, it has geat chances of beeing
73 // if token has a class, it has geat chances of beeing
71 // of interest. Add it to the list of possible completions.
74 // of interest. Add it to the list of possible completions.
72 // we could skip token of ClassName 'comment'
75 // we could skip token of ClassName 'comment'
73 // or 'number' and 'operator'
76 // or 'number' and 'operator'
74 if (tk.className != null) {
77 if (tk.className !== null) {
75 maybeAdd(tk.string);
78 maybeAdd(tk.string);
76 }
77 // jump to char after end of current token
78 c = tk.end;
79 }
79 }
80 // jump to char after end of current token
81 c = tk.end;
80 }
82 }
81 return found;
82 }
83 }
84 return found;
85 };
83
86
84
87 var getCompletions = function(token, editor) {
85 function getCompletions(token, editor) {
86 var candidates = getAllTokens(editor);
88 var candidates = getAllTokens(editor);
87 // filter all token that have a common start (but nox exactly) the lenght of the current token
89 // filter all token that have a common start (but nox exactly) the lenght of the current token
88 var lambda = function (x) {
90 var lambda = function (x) {
89 return (x.indexOf(token.string) == 0 && x != token.string)
91 return (x.indexOf(token.string) === 0 && x != token.string);
90 };
92 };
91 var filterd = candidates.filter(lambda);
93 var filterd = candidates.filter(lambda);
92 return filterd;
94 return filterd;
93 }
95 };
94 })();
96
97 return {'contextHint': CodeMirror.contextHint};
98 });
This diff has been collapsed as it changes many lines, (902 lines changed) Show them Hide them
@@ -1,468 +1,476 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Keyboard management
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 'base/js/utils',
12 var IPython = (function (IPython) {
8 'base/js/keyboard',
9 ], function(IPython, $, utils, keyboard) {
13 "use strict";
10 "use strict";
11
12 var browser = utils.browser[0];
13 var platform = utils.platform;
14
14
15 var browser = IPython.utils.browser[0];
15 // Main keyboard manager for the notebook
16 var platform = IPython.utils.platform;
16 var keycodes = keyboard.keycodes;
17
17
18 // Default keyboard shortcuts
18 var KeyboardManager = function (options) {
19 // Constructor
20 //
21 // Parameters:
22 // options: dictionary
23 // Dictionary of keyword arguments.
24 // events: $(Events) instance
25 // pager: Pager instance
26 this.mode = 'command';
27 this.enabled = true;
28 this.pager = options.pager;
29 this.quick_help = undefined;
30 this.notebook = undefined;
31 this.bind_events();
32 this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
33 this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
34 this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
35 this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
36 this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
37 this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
38 };
19
39
20 var default_common_shortcuts = {
40 KeyboardManager.prototype.get_default_common_shortcuts = function() {
21 'shift' : {
41 var that = this;
22 help : '',
42 var shortcuts = {
23 help_index : '',
43 'shift' : {
24 handler : function (event) {
44 help : '',
25 // ignore shift keydown
45 help_index : '',
26 return true;
46 handler : function (event) {
27 }
47 // ignore shift keydown
28 },
48 return true;
29 'shift-enter' : {
49 }
30 help : 'run cell, select below',
50 },
31 help_index : 'ba',
51 'shift-enter' : {
32 handler : function (event) {
52 help : 'run cell, select below',
33 IPython.notebook.execute_cell_and_select_below();
53 help_index : 'ba',
34 return false;
54 handler : function (event) {
35 }
55 that.notebook.execute_cell_and_select_below();
36 },
56 return false;
37 'ctrl-enter' : {
57 }
38 help : 'run cell',
58 },
39 help_index : 'bb',
59 'ctrl-enter' : {
40 handler : function (event) {
60 help : 'run cell',
41 IPython.notebook.execute_cell();
61 help_index : 'bb',
42 return false;
62 handler : function (event) {
43 }
63 that.notebook.execute_cell();
44 },
64 return false;
45 'alt-enter' : {
65 }
46 help : 'run cell, insert below',
66 },
47 help_index : 'bc',
67 'alt-enter' : {
48 handler : function (event) {
68 help : 'run cell, insert below',
49 IPython.notebook.execute_cell_and_insert_below();
69 help_index : 'bc',
50 return false;
70 handler : function (event) {
71 that.notebook.execute_cell_and_insert_below();
72 return false;
73 }
51 }
74 }
75 };
76
77 if (platform === 'MacOS') {
78 shortcuts['cmd-s'] =
79 {
80 help : 'save notebook',
81 help_index : 'fb',
82 handler : function (event) {
83 that.notebook.save_checkpoint();
84 event.preventDefault();
85 return false;
86 }
87 };
88 } else {
89 shortcuts['ctrl-s'] =
90 {
91 help : 'save notebook',
92 help_index : 'fb',
93 handler : function (event) {
94 that.notebook.save_checkpoint();
95 event.preventDefault();
96 return false;
97 }
98 };
52 }
99 }
100 return shortcuts;
53 };
101 };
54
102
55 if (platform === 'MacOS') {
103 KeyboardManager.prototype.get_default_edit_shortcuts = function() {
56 default_common_shortcuts['cmd-s'] =
104 var that = this;
57 {
105 return {
58 help : 'save notebook',
106 'esc' : {
59 help_index : 'fb',
107 help : 'command mode',
108 help_index : 'aa',
60 handler : function (event) {
109 handler : function (event) {
61 IPython.notebook.save_checkpoint();
110 that.notebook.command_mode();
62 event.preventDefault();
63 return false;
111 return false;
64 }
112 }
65 };
113 },
66 } else {
114 'ctrl-m' : {
67 default_common_shortcuts['ctrl-s'] =
115 help : 'command mode',
68 {
116 help_index : 'ab',
69 help : 'save notebook',
70 help_index : 'fb',
71 handler : function (event) {
117 handler : function (event) {
72 IPython.notebook.save_checkpoint();
118 that.notebook.command_mode();
73 event.preventDefault();
74 return false;
119 return false;
75 }
120 }
76 };
121 },
77 }
122 'up' : {
78
123 help : '',
79 // Edit mode defaults
124 help_index : '',
80
125 handler : function (event) {
81 var default_edit_shortcuts = {
126 var index = that.notebook.get_selected_index();
82 'esc' : {
127 var cell = that.notebook.get_cell(index);
83 help : 'command mode',
128 if (cell && cell.at_top() && index !== 0) {
84 help_index : 'aa',
129 event.preventDefault();
85 handler : function (event) {
130 that.notebook.command_mode();
86 IPython.notebook.command_mode();
131 that.notebook.select_prev();
87 return false;
132 that.notebook.edit_mode();
88 }
133 var cm = that.notebook.get_selected_cell().code_mirror;
89 },
134 cm.setCursor(cm.lastLine(), 0);
90 'ctrl-m' : {
135 return false;
91 help : 'command mode',
136 } else if (cell) {
92 help_index : 'ab',
137 var cm = cell.code_mirror;
93 handler : function (event) {
138 cm.execCommand('goLineUp');
94 IPython.notebook.command_mode();
139 return false;
95 return false;
140 }
96 }
141 }
97 },
142 },
98 'up' : {
143 'down' : {
99 help : '',
144 help : '',
100 help_index : '',
145 help_index : '',
101 handler : function (event) {
146 handler : function (event) {
102 var index = IPython.notebook.get_selected_index();
147 var index = that.notebook.get_selected_index();
103 var cell = IPython.notebook.get_cell(index);
148 var cell = that.notebook.get_cell(index);
104 if (cell && cell.at_top() && index !== 0) {
149 if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
105 event.preventDefault();
150 event.preventDefault();
106 IPython.notebook.command_mode();
151 that.notebook.command_mode();
107 IPython.notebook.select_prev();
152 that.notebook.select_next();
108 IPython.notebook.edit_mode();
153 that.notebook.edit_mode();
109 var cm = IPython.notebook.get_selected_cell().code_mirror;
154 var cm = that.notebook.get_selected_cell().code_mirror;
110 cm.setCursor(cm.lastLine(), 0);
155 cm.setCursor(0, 0);
111 return false;
156 return false;
112 } else if (cell) {
157 } else {
113 var cm = cell.code_mirror;
158 var cm = cell.code_mirror;
114 cm.execCommand('goLineUp');
159 cm.execCommand('goLineDown');
160 return false;
161 }
162 }
163 },
164 'ctrl-shift--' : {
165 help : 'split cell',
166 help_index : 'ea',
167 handler : function (event) {
168 that.notebook.split_cell();
115 return false;
169 return false;
116 }
170 }
117 }
171 },
118 },
172 'ctrl-shift-subtract' : {
119 'down' : {
173 help : '',
120 help : '',
174 help_index : 'eb',
121 help_index : '',
175 handler : function (event) {
122 handler : function (event) {
176 that.notebook.split_cell();
123 var index = IPython.notebook.get_selected_index();
124 var cell = IPython.notebook.get_cell(index);
125 if (cell.at_bottom() && index !== (IPython.notebook.ncells()-1)) {
126 event.preventDefault();
127 IPython.notebook.command_mode();
128 IPython.notebook.select_next();
129 IPython.notebook.edit_mode();
130 var cm = IPython.notebook.get_selected_cell().code_mirror;
131 cm.setCursor(0, 0);
132 return false;
133 } else {
134 var cm = cell.code_mirror;
135 cm.execCommand('goLineDown');
136 return false;
177 return false;
137 }
178 }
138 }
179 },
139 },
180 };
140 'ctrl-shift--' : {
141 help : 'split cell',
142 help_index : 'ea',
143 handler : function (event) {
144 IPython.notebook.split_cell();
145 return false;
146 }
147 },
148 'ctrl-shift-subtract' : {
149 help : '',
150 help_index : 'eb',
151 handler : function (event) {
152 IPython.notebook.split_cell();
153 return false;
154 }
155 },
156 };
181 };
157
182
158 // Command mode defaults
183 KeyboardManager.prototype.get_default_command_shortcuts = function() {
159
184 var that = this;
160 var default_command_shortcuts = {
185 return {
161 'enter' : {
186 'enter' : {
162 help : 'edit mode',
187 help : 'edit mode',
163 help_index : 'aa',
188 help_index : 'aa',
164 handler : function (event) {
189 handler : function (event) {
165 IPython.notebook.edit_mode();
190 that.notebook.edit_mode();
166 return false;
191 return false;
167 }
168 },
169 'up' : {
170 help : 'select previous cell',
171 help_index : 'da',
172 handler : function (event) {
173 var index = IPython.notebook.get_selected_index();
174 if (index !== 0 && index !== null) {
175 IPython.notebook.select_prev();
176 IPython.notebook.focus_cell();
177 }
192 }
178 return false;
193 },
179 }
194 'up' : {
180 },
195 help : 'select previous cell',
181 'down' : {
196 help_index : 'da',
182 help : 'select next cell',
197 handler : function (event) {
183 help_index : 'db',
198 var index = that.notebook.get_selected_index();
184 handler : function (event) {
199 if (index !== 0 && index !== null) {
185 var index = IPython.notebook.get_selected_index();
200 that.notebook.select_prev();
186 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
201 that.notebook.focus_cell();
187 IPython.notebook.select_next();
202 }
188 IPython.notebook.focus_cell();
203 return false;
189 }
204 }
190 return false;
205 },
191 }
206 'down' : {
192 },
207 help : 'select next cell',
193 'k' : {
208 help_index : 'db',
194 help : 'select previous cell',
209 handler : function (event) {
195 help_index : 'dc',
210 var index = that.notebook.get_selected_index();
196 handler : function (event) {
211 if (index !== (that.notebook.ncells()-1) && index !== null) {
197 var index = IPython.notebook.get_selected_index();
212 that.notebook.select_next();
198 if (index !== 0 && index !== null) {
213 that.notebook.focus_cell();
199 IPython.notebook.select_prev();
214 }
200 IPython.notebook.focus_cell();
215 return false;
201 }
216 }
202 return false;
217 },
203 }
218 'k' : {
204 },
219 help : 'select previous cell',
205 'j' : {
220 help_index : 'dc',
206 help : 'select next cell',
221 handler : function (event) {
207 help_index : 'dd',
222 var index = that.notebook.get_selected_index();
208 handler : function (event) {
223 if (index !== 0 && index !== null) {
209 var index = IPython.notebook.get_selected_index();
224 that.notebook.select_prev();
210 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
225 that.notebook.focus_cell();
211 IPython.notebook.select_next();
226 }
212 IPython.notebook.focus_cell();
227 return false;
213 }
228 }
214 return false;
229 },
215 }
230 'j' : {
216 },
231 help : 'select next cell',
217 'x' : {
232 help_index : 'dd',
218 help : 'cut cell',
233 handler : function (event) {
219 help_index : 'ee',
234 var index = that.notebook.get_selected_index();
220 handler : function (event) {
235 if (index !== (that.notebook.ncells()-1) && index !== null) {
221 IPython.notebook.cut_cell();
236 that.notebook.select_next();
222 return false;
237 that.notebook.focus_cell();
223 }
238 }
224 },
239 return false;
225 'c' : {
240 }
226 help : 'copy cell',
241 },
227 help_index : 'ef',
242 'x' : {
228 handler : function (event) {
243 help : 'cut cell',
229 IPython.notebook.copy_cell();
244 help_index : 'ee',
230 return false;
245 handler : function (event) {
231 }
246 that.notebook.cut_cell();
232 },
247 return false;
233 'shift-v' : {
248 }
234 help : 'paste cell above',
249 },
235 help_index : 'eg',
250 'c' : {
236 handler : function (event) {
251 help : 'copy cell',
237 IPython.notebook.paste_cell_above();
252 help_index : 'ef',
238 return false;
253 handler : function (event) {
239 }
254 that.notebook.copy_cell();
240 },
255 return false;
241 'v' : {
256 }
242 help : 'paste cell below',
257 },
243 help_index : 'eh',
258 'shift-v' : {
244 handler : function (event) {
259 help : 'paste cell above',
245 IPython.notebook.paste_cell_below();
260 help_index : 'eg',
246 return false;
261 handler : function (event) {
247 }
262 that.notebook.paste_cell_above();
248 },
263 return false;
249 'd' : {
264 }
250 help : 'delete cell (press twice)',
265 },
251 help_index : 'ej',
266 'v' : {
252 count: 2,
267 help : 'paste cell below',
253 handler : function (event) {
268 help_index : 'eh',
254 IPython.notebook.delete_cell();
269 handler : function (event) {
255 return false;
270 that.notebook.paste_cell_below();
256 }
271 return false;
257 },
272 }
258 'a' : {
273 },
259 help : 'insert cell above',
274 'd' : {
260 help_index : 'ec',
275 help : 'delete cell (press twice)',
261 handler : function (event) {
276 help_index : 'ej',
262 IPython.notebook.insert_cell_above();
277 count: 2,
263 IPython.notebook.select_prev();
278 handler : function (event) {
264 IPython.notebook.focus_cell();
279 that.notebook.delete_cell();
265 return false;
280 return false;
266 }
281 }
267 },
282 },
268 'b' : {
283 'a' : {
269 help : 'insert cell below',
284 help : 'insert cell above',
270 help_index : 'ed',
285 help_index : 'ec',
271 handler : function (event) {
286 handler : function (event) {
272 IPython.notebook.insert_cell_below();
287 that.notebook.insert_cell_above();
273 IPython.notebook.select_next();
288 that.notebook.select_prev();
274 IPython.notebook.focus_cell();
289 that.notebook.focus_cell();
275 return false;
290 return false;
276 }
291 }
277 },
292 },
278 'y' : {
293 'b' : {
279 help : 'to code',
294 help : 'insert cell below',
280 help_index : 'ca',
295 help_index : 'ed',
281 handler : function (event) {
296 handler : function (event) {
282 IPython.notebook.to_code();
297 that.notebook.insert_cell_below();
283 return false;
298 that.notebook.select_next();
284 }
299 that.notebook.focus_cell();
285 },
300 return false;
286 'm' : {
301 }
287 help : 'to markdown',
302 },
288 help_index : 'cb',
303 'y' : {
289 handler : function (event) {
304 help : 'to code',
290 IPython.notebook.to_markdown();
305 help_index : 'ca',
291 return false;
306 handler : function (event) {
292 }
307 that.notebook.to_code();
293 },
308 return false;
294 'r' : {
309 }
295 help : 'to raw',
310 },
296 help_index : 'cc',
311 'm' : {
297 handler : function (event) {
312 help : 'to markdown',
298 IPython.notebook.to_raw();
313 help_index : 'cb',
299 return false;
314 handler : function (event) {
300 }
315 that.notebook.to_markdown();
301 },
316 return false;
302 '1' : {
317 }
303 help : 'to heading 1',
318 },
304 help_index : 'cd',
319 'r' : {
305 handler : function (event) {
320 help : 'to raw',
306 IPython.notebook.to_heading(undefined, 1);
321 help_index : 'cc',
307 return false;
322 handler : function (event) {
308 }
323 that.notebook.to_raw();
309 },
324 return false;
310 '2' : {
325 }
311 help : 'to heading 2',
326 },
312 help_index : 'ce',
327 '1' : {
313 handler : function (event) {
328 help : 'to heading 1',
314 IPython.notebook.to_heading(undefined, 2);
329 help_index : 'cd',
315 return false;
330 handler : function (event) {
316 }
331 that.notebook.to_heading(undefined, 1);
317 },
332 return false;
318 '3' : {
333 }
319 help : 'to heading 3',
334 },
320 help_index : 'cf',
335 '2' : {
321 handler : function (event) {
336 help : 'to heading 2',
322 IPython.notebook.to_heading(undefined, 3);
337 help_index : 'ce',
323 return false;
338 handler : function (event) {
324 }
339 that.notebook.to_heading(undefined, 2);
325 },
340 return false;
326 '4' : {
341 }
327 help : 'to heading 4',
342 },
328 help_index : 'cg',
343 '3' : {
329 handler : function (event) {
344 help : 'to heading 3',
330 IPython.notebook.to_heading(undefined, 4);
345 help_index : 'cf',
331 return false;
346 handler : function (event) {
332 }
347 that.notebook.to_heading(undefined, 3);
333 },
348 return false;
334 '5' : {
349 }
335 help : 'to heading 5',
350 },
336 help_index : 'ch',
351 '4' : {
337 handler : function (event) {
352 help : 'to heading 4',
338 IPython.notebook.to_heading(undefined, 5);
353 help_index : 'cg',
339 return false;
354 handler : function (event) {
340 }
355 that.notebook.to_heading(undefined, 4);
341 },
356 return false;
342 '6' : {
357 }
343 help : 'to heading 6',
358 },
344 help_index : 'ci',
359 '5' : {
345 handler : function (event) {
360 help : 'to heading 5',
346 IPython.notebook.to_heading(undefined, 6);
361 help_index : 'ch',
347 return false;
362 handler : function (event) {
348 }
363 that.notebook.to_heading(undefined, 5);
349 },
364 return false;
350 'o' : {
365 }
351 help : 'toggle output',
366 },
352 help_index : 'gb',
367 '6' : {
353 handler : function (event) {
368 help : 'to heading 6',
354 IPython.notebook.toggle_output();
369 help_index : 'ci',
355 return false;
370 handler : function (event) {
356 }
371 that.notebook.to_heading(undefined, 6);
357 },
372 return false;
358 'shift-o' : {
373 }
359 help : 'toggle output scrolling',
374 },
360 help_index : 'gc',
375 'o' : {
361 handler : function (event) {
376 help : 'toggle output',
362 IPython.notebook.toggle_output_scroll();
377 help_index : 'gb',
363 return false;
378 handler : function (event) {
364 }
379 that.notebook.toggle_output();
365 },
380 return false;
366 's' : {
381 }
367 help : 'save notebook',
382 },
368 help_index : 'fa',
383 'shift-o' : {
369 handler : function (event) {
384 help : 'toggle output scrolling',
370 IPython.notebook.save_checkpoint();
385 help_index : 'gc',
371 return false;
386 handler : function (event) {
372 }
387 that.notebook.toggle_output_scroll();
373 },
388 return false;
374 'ctrl-j' : {
389 }
375 help : 'move cell down',
390 },
376 help_index : 'eb',
391 's' : {
377 handler : function (event) {
392 help : 'save notebook',
378 IPython.notebook.move_cell_down();
393 help_index : 'fa',
379 return false;
394 handler : function (event) {
380 }
395 that.notebook.save_checkpoint();
381 },
396 return false;
382 'ctrl-k' : {
397 }
383 help : 'move cell up',
398 },
384 help_index : 'ea',
399 'ctrl-j' : {
385 handler : function (event) {
400 help : 'move cell down',
386 IPython.notebook.move_cell_up();
401 help_index : 'eb',
387 return false;
402 handler : function (event) {
388 }
403 that.notebook.move_cell_down();
389 },
404 return false;
390 'l' : {
405 }
391 help : 'toggle line numbers',
406 },
392 help_index : 'ga',
407 'ctrl-k' : {
393 handler : function (event) {
408 help : 'move cell up',
394 IPython.notebook.cell_toggle_line_numbers();
409 help_index : 'ea',
395 return false;
410 handler : function (event) {
396 }
411 that.notebook.move_cell_up();
397 },
412 return false;
398 'i' : {
413 }
399 help : 'interrupt kernel (press twice)',
414 },
400 help_index : 'ha',
415 'l' : {
401 count: 2,
416 help : 'toggle line numbers',
402 handler : function (event) {
417 help_index : 'ga',
403 IPython.notebook.kernel.interrupt();
418 handler : function (event) {
404 return false;
419 that.notebook.cell_toggle_line_numbers();
405 }
420 return false;
406 },
421 }
407 '0' : {
422 },
408 help : 'restart kernel (press twice)',
423 'i' : {
409 help_index : 'hb',
424 help : 'interrupt kernel (press twice)',
410 count: 2,
425 help_index : 'ha',
411 handler : function (event) {
426 count: 2,
412 IPython.notebook.restart_kernel();
427 handler : function (event) {
413 return false;
428 that.notebook.kernel.interrupt();
414 }
429 return false;
415 },
430 }
416 'h' : {
431 },
417 help : 'keyboard shortcuts',
432 '0' : {
418 help_index : 'ge',
433 help : 'restart kernel (press twice)',
419 handler : function (event) {
434 help_index : 'hb',
420 IPython.quick_help.show_keyboard_shortcuts();
435 count: 2,
421 return false;
436 handler : function (event) {
422 }
437 that.notebook.restart_kernel();
423 },
438 return false;
424 'z' : {
439 }
425 help : 'undo last delete',
440 },
426 help_index : 'ei',
441 'h' : {
427 handler : function (event) {
442 help : 'keyboard shortcuts',
428 IPython.notebook.undelete_cell();
443 help_index : 'ge',
429 return false;
444 handler : function (event) {
430 }
445 that.quick_help.show_keyboard_shortcuts();
431 },
446 return false;
432 'shift-m' : {
447 }
433 help : 'merge cell below',
448 },
434 help_index : 'ek',
449 'z' : {
435 handler : function (event) {
450 help : 'undo last delete',
436 IPython.notebook.merge_cell_below();
451 help_index : 'ei',
437 return false;
452 handler : function (event) {
438 }
453 that.notebook.undelete_cell();
439 },
454 return false;
440 'q' : {
455 }
441 help : 'close pager',
456 },
442 help_index : 'gd',
457 'shift-m' : {
443 handler : function (event) {
458 help : 'merge cell below',
444 IPython.pager.collapse();
459 help_index : 'ek',
445 return false;
460 handler : function (event) {
446 }
461 that.notebook.merge_cell_below();
447 },
462 return false;
448 };
463 }
449
464 },
450
465 'q' : {
451 // Main keyboard manager for the notebook
466 help : 'close pager',
452
467 help_index : 'gd',
453 var ShortcutManager = IPython.keyboard.ShortcutManager;
468 handler : function (event) {
454 var keycodes = IPython.keyboard.keycodes;
469 that.pager.collapse();
455
470 return false;
456 var KeyboardManager = function () {
471 }
457 this.mode = 'command';
472 },
458 this.enabled = true;
473 };
459 this.bind_events();
460 this.command_shortcuts = new ShortcutManager();
461 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
462 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
463 this.edit_shortcuts = new ShortcutManager();
464 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
465 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
466 };
474 };
467
475
468 KeyboardManager.prototype.bind_events = function () {
476 KeyboardManager.prototype.bind_events = function () {
@@ -473,7 +481,7 b' var IPython = (function (IPython) {'
473 };
481 };
474
482
475 KeyboardManager.prototype.handle_keydown = function (event) {
483 KeyboardManager.prototype.handle_keydown = function (event) {
476 var notebook = IPython.notebook;
484 var notebook = this.notebook;
477
485
478 if (event.which === keycodes.esc) {
486 if (event.which === keycodes.esc) {
479 // Intercept escape at highest level to avoid closing
487 // Intercept escape at highest level to avoid closing
@@ -545,18 +553,14 b' var IPython = (function (IPython) {'
545 // is_focused must be used to check for the case where an element within
553 // is_focused must be used to check for the case where an element within
546 // the element being removed is focused.
554 // the element being removed is focused.
547 e.on('remove', function () {
555 e.on('remove', function () {
548 if (IPython.utils.is_focused(e[0])) {
556 if (utils.is_focused(e[0])) {
549 that.enable();
557 that.enable();
550 }
558 }
551 });
559 });
552 };
560 };
553
561
554
562 // For backwards compatability.
555 IPython.default_common_shortcuts = default_common_shortcuts;
556 IPython.default_edit_shortcuts = default_edit_shortcuts;
557 IPython.default_command_shortcuts = default_command_shortcuts;
558 IPython.KeyboardManager = KeyboardManager;
563 IPython.KeyboardManager = KeyboardManager;
559
564
560 return IPython;
565 return {'KeyboardManager': KeyboardManager};
561
566 });
562 }(IPython));
@@ -1,19 +1,15 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 ], function(IPython, $) {
8 //============================================================================
9 // Layout
10 //============================================================================
11
12 var IPython = (function (IPython) {
13 "use strict";
8 "use strict";
14
9
15 var LayoutManager = function () {
10 var LayoutManager = function () {
16 this.bind_events();
11 this.bind_events();
12 this.pager = undefined;
17 };
13 };
18
14
19 LayoutManager.prototype.bind_events = function () {
15 LayoutManager.prototype.bind_events = function () {
@@ -43,19 +39,20 b' var IPython = (function (IPython) {'
43 var app_height = this.app_height(); // content height
39 var app_height = this.app_height(); // content height
44
40
45 $('#ipython-main-app').height(app_height); // content+padding+border height
41 $('#ipython-main-app').height(app_height); // content+padding+border height
46
42 if (this.pager) {
47 var pager_height = IPython.pager.percentage_height*app_height;
43 var pager_height = this.pager.percentage_height*app_height;
48 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
44 var pager_splitter_height = $('div#pager_splitter').outerHeight(true);
49 $('div#pager').outerHeight(pager_height);
45 $('div#pager').outerHeight(pager_height);
50 if (IPython.pager.expanded) {
46 if (this.pager.expanded) {
51 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
47 $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height);
52 } else {
48 } else {
53 $('div#notebook').outerHeight(app_height-pager_splitter_height);
49 $('div#notebook').outerHeight(app_height-pager_splitter_height);
50 }
54 }
51 }
55 };
52 };
56
53
54 // Backwards compatability.
57 IPython.LayoutManager = LayoutManager;
55 IPython.LayoutManager = LayoutManager;
58
56
59 return IPython;
57 return {'LayoutManager': LayoutManager};
60
58 });
61 }(IPython));
@@ -1,78 +1,96 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 require([
9 // On document ready
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 'notebook/js/notebook',
12 // for the time beeing, we have to pass marked as a parameter here,
8 'base/js/utils',
13 // as injecting require.js make marked not to put itself in the globals,
9 'base/js/page',
14 // which make both this file fail at setting marked configuration, and textcell.js
10 'notebook/js/layoutmanager',
15 // which search marked into global.
11 'base/js/events',
16 require(['components/marked/lib/marked',
12 'auth/js/loginwidget',
17 'widgets/js/init',
13 'notebook/js/maintoolbar',
18 'components/bootstrap-tour/build/js/bootstrap-tour.min'],
14 'notebook/js/pager',
19
15 'notebook/js/quickhelp',
20 function (marked) {
16 'notebook/js/menubar',
17 'notebook/js/notificationarea',
18 'notebook/js/savewidget',
19 'notebook/js/keyboardmanager',
20 'notebook/js/config',
21 'notebook/js/kernelselector',
22 // only loaded, not used:
23 'custom/custom',
24 ], function(
25 IPython,
26 $,
27 notebook,
28 utils,
29 page,
30 layoutmanager,
31 events,
32 loginwidget,
33 maintoolbar,
34 pager,
35 quickhelp,
36 menubar,
37 notificationarea,
38 savewidget,
39 keyboardmanager,
40 config,
41 kernelselector
42 ) {
21 "use strict";
43 "use strict";
22
44
23 window.marked = marked;
45 var common_options = {
24
46 base_url : utils.get_body_data("baseUrl"),
25 // monkey patch CM to be able to syntax highlight cell magics
47 ws_url : IPython.utils.get_body_data("wsUrl"),
26 // bug reported upstream,
48 notebook_path : utils.get_body_data("notebookPath"),
27 // see https://github.com/marijnh/CodeMirror2/issues/670
49 notebook_name : utils.get_body_data('notebookName')
28 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
29 console.log('patching CM for undefined indent');
30 CodeMirror.modes.null = function() {
31 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
32 };
33 }
34
35 CodeMirror.patchedGetMode = function(config, mode){
36 var cmmode = CodeMirror.getMode(config, mode);
37 if(cmmode.indent === null) {
38 console.log('patch mode "' , mode, '" on the fly');
39 cmmode.indent = function(){return 0;};
40 }
41 return cmmode;
42 };
43 // end monkey patching CodeMirror
44
45 IPython.mathjaxutils.init();
46
47 $('#ipython-main-app').addClass('border-box-sizing');
48 $('div#notebook_panel').addClass('border-box-sizing');
49
50 var opts = {
51 base_url : IPython.utils.get_body_data("baseUrl"),
52 notebook_path : IPython.utils.get_body_data("notebookPath"),
53 notebook_name : IPython.utils.get_body_data('notebookName')
54 };
50 };
55
51
56 IPython.page = new IPython.Page();
52 var user_config = $.extend({}, config.default_config);
57 IPython.layout_manager = new IPython.LayoutManager();
53 var page = new page.Page();
58 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
54 var layout_manager = new layoutmanager.LayoutManager();
59 IPython.quick_help = new IPython.QuickHelp();
55 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
60 try {
56 layout_manager: layout_manager,
61 IPython.tour = new IPython.NotebookTour();
57 events: events});
62 } catch (e) {
58 var keyboard_manager = new keyboardmanager.KeyboardManager({
63 console.log("Failed to instantiate Notebook Tour", e);
59 pager: pager,
64 }
60 events: events});
65 IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts);
61 var save_widget = new savewidget.SaveWidget('span#save_widget', {
66 IPython.notebook = new IPython.Notebook('div#notebook', opts);
62 events: events,
67 IPython.keyboard_manager = new IPython.KeyboardManager();
63 keyboard_manager: keyboard_manager});
68 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
64 var notebook = new notebook.Notebook('div#notebook', $.extend({
69 IPython.menubar = new IPython.MenuBar('#menubar', opts);
65 events: events,
70 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container');
66 keyboard_manager: keyboard_manager,
71 IPython.tooltip = new IPython.Tooltip();
67 save_widget: save_widget,
72 IPython.notification_area = new IPython.NotificationArea('#notification_area');
68 config: user_config},
73 IPython.notification_area.init_notification_widgets();
69 common_options));
74
70 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
75 IPython.layout_manager.do_resize();
71 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
72 notebook: notebook,
73 events: events});
74 var quick_help = new quickhelp.QuickHelp({
75 keyboard_manager: keyboard_manager,
76 events: events,
77 notebook: notebook});
78 var menubar = new menubar.MenuBar('#menubar', $.extend({
79 notebook: notebook,
80 layout_manager: layout_manager,
81 events: events,
82 save_widget: save_widget,
83 quick_help: quick_help},
84 common_options));
85 var notification_area = new notificationarea.NotificationArea(
86 '#notification_area', {
87 events: events,
88 save_widget: save_widget,
89 notebook: notebook,
90 keyboard_manager: keyboard_manager});
91 notification_area.init_notification_widgets();
92 var kernel_selector = new kernelselector.KernelSelector(
93 '#kernel_selector_widget', notebook);
76
94
77 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
95 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
78 '<span id="test2" style="font-weight: bold;">x</span>'+
96 '<span id="test2" style="font-weight: bold;">x</span>'+
@@ -85,43 +103,37 b' function (marked) {'
85 }
103 }
86 $('#fonttest').remove();
104 $('#fonttest').remove();
87
105
88 IPython.page.show();
106 page.show();
89
107
90 IPython.layout_manager.do_resize();
108 layout_manager.do_resize();
91 var first_load = function () {
109 var first_load = function () {
92 IPython.layout_manager.do_resize();
110 layout_manager.do_resize();
93 var hash = document.location.hash;
111 var hash = document.location.hash;
94 if (hash) {
112 if (hash) {
95 document.location.hash = '';
113 document.location.hash = '';
96 document.location.hash = hash;
114 document.location.hash = hash;
97 }
115 }
98 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
116 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
99 // only do this once
117 // only do this once
100 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
118 events.off('notebook_loaded.Notebook', first_load);
101 };
119 };
120 events.on('notebook_loaded.Notebook', first_load);
102
121
103 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
122 IPython.page = page;
104 $([IPython.events]).trigger('app_initialized.NotebookApp');
123 IPython.layout_manager = layout_manager;
105 IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path);
124 IPython.notebook = notebook;
125 IPython.pager = pager;
126 IPython.quick_help = quick_help;
127 IPython.login_widget = login_widget;
128 IPython.menubar = menubar;
129 IPython.toolbar = toolbar;
130 IPython.notification_area = notification_area;
131 IPython.keyboard_manager = keyboard_manager;
132 IPython.save_widget = save_widget;
133 IPython.config = user_config;
134 IPython.tooltip = notebook.tooltip;
135
136 events.trigger('app_initialized.NotebookApp');
137 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
106
138
107 if (marked) {
108 marked.setOptions({
109 gfm : true,
110 tables: true,
111 langPrefix: "language-",
112 highlight: function(code, lang) {
113 if (!lang) {
114 // no language, no highlight
115 return code;
116 }
117 var highlighted;
118 try {
119 highlighted = hljs.highlight(lang, code, false);
120 } catch(err) {
121 highlighted = hljs.highlightAuto(code);
122 }
123 return highlighted.value;
124 }
125 });
126 }
127 });
139 });
@@ -1,35 +1,43 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 'notebook/js/toolbar',
8 //============================================================================
8 'notebook/js/celltoolbar',
9 // ToolBar
9 ], function(IPython, $, toolbar, celltoolbar) {
10 //============================================================================
11
12 var IPython = (function (IPython) {
13 "use strict";
10 "use strict";
14
11
15 var MainToolBar = function (selector) {
12 var MainToolBar = function (selector, options) {
16 IPython.ToolBar.apply(this, arguments);
13 // Constructor
14 //
15 // Parameters:
16 // selector: string
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // events: $(Events) instance
20 // notebook: Notebook instance
21 toolbar.ToolBar.apply(this, arguments);
22 this.events = options.events;
23 this.notebook = options.notebook;
17 this.construct();
24 this.construct();
18 this.add_celltype_list();
25 this.add_celltype_list();
19 this.add_celltoolbar_list();
26 this.add_celltoolbar_list();
20 this.bind_events();
27 this.bind_events();
21 };
28 };
22
29
23 MainToolBar.prototype = new IPython.ToolBar();
30 MainToolBar.prototype = new toolbar.ToolBar();
24
31
25 MainToolBar.prototype.construct = function () {
32 MainToolBar.prototype.construct = function () {
33 var that = this;
26 this.add_buttons_group([
34 this.add_buttons_group([
27 {
35 {
28 id : 'save_b',
36 id : 'save_b',
29 label : 'Save and Checkpoint',
37 label : 'Save and Checkpoint',
30 icon : 'icon-save',
38 icon : 'fa-save',
31 callback : function () {
39 callback : function () {
32 IPython.notebook.save_checkpoint();
40 that.notebook.save_checkpoint();
33 }
41 }
34 }
42 }
35 ]);
43 ]);
@@ -38,11 +46,11 b' var IPython = (function (IPython) {'
38 {
46 {
39 id : 'insert_below_b',
47 id : 'insert_below_b',
40 label : 'Insert Cell Below',
48 label : 'Insert Cell Below',
41 icon : 'icon-plus-sign',
49 icon : 'fa-plus',
42 callback : function () {
50 callback : function () {
43 IPython.notebook.insert_cell_below('code');
51 that.notebook.insert_cell_below('code');
44 IPython.notebook.select_next();
52 that.notebook.select_next();
45 IPython.notebook.focus_cell();
53 that.notebook.focus_cell();
46 }
54 }
47 }
55 }
48 ],'insert_above_below');
56 ],'insert_above_below');
@@ -51,25 +59,25 b' var IPython = (function (IPython) {'
51 {
59 {
52 id : 'cut_b',
60 id : 'cut_b',
53 label : 'Cut Cell',
61 label : 'Cut Cell',
54 icon : 'icon-cut',
62 icon : 'fa-cut',
55 callback : function () {
63 callback : function () {
56 IPython.notebook.cut_cell();
64 that.notebook.cut_cell();
57 }
65 }
58 },
66 },
59 {
67 {
60 id : 'copy_b',
68 id : 'copy_b',
61 label : 'Copy Cell',
69 label : 'Copy Cell',
62 icon : 'icon-copy',
70 icon : 'fa-copy',
63 callback : function () {
71 callback : function () {
64 IPython.notebook.copy_cell();
72 that.notebook.copy_cell();
65 }
73 }
66 },
74 },
67 {
75 {
68 id : 'paste_b',
76 id : 'paste_b',
69 label : 'Paste Cell Below',
77 label : 'Paste Cell Below',
70 icon : 'icon-paste',
78 icon : 'fa-paste',
71 callback : function () {
79 callback : function () {
72 IPython.notebook.paste_cell_below();
80 that.notebook.paste_cell_below();
73 }
81 }
74 }
82 }
75 ],'cut_copy_paste');
83 ],'cut_copy_paste');
@@ -78,17 +86,17 b' var IPython = (function (IPython) {'
78 {
86 {
79 id : 'move_up_b',
87 id : 'move_up_b',
80 label : 'Move Cell Up',
88 label : 'Move Cell Up',
81 icon : 'icon-arrow-up',
89 icon : 'fa-arrow-up',
82 callback : function () {
90 callback : function () {
83 IPython.notebook.move_cell_up();
91 that.notebook.move_cell_up();
84 }
92 }
85 },
93 },
86 {
94 {
87 id : 'move_down_b',
95 id : 'move_down_b',
88 label : 'Move Cell Down',
96 label : 'Move Cell Down',
89 icon : 'icon-arrow-down',
97 icon : 'fa-arrow-down',
90 callback : function () {
98 callback : function () {
91 IPython.notebook.move_cell_down();
99 that.notebook.move_cell_down();
92 }
100 }
93 }
101 }
94 ],'move_up_down');
102 ],'move_up_down');
@@ -98,26 +106,26 b' var IPython = (function (IPython) {'
98 {
106 {
99 id : 'run_b',
107 id : 'run_b',
100 label : 'Run Cell',
108 label : 'Run Cell',
101 icon : 'icon-play',
109 icon : 'fa-play',
102 callback : function () {
110 callback : function () {
103 // emulate default shift-enter behavior
111 // emulate default shift-enter behavior
104 IPython.notebook.execute_cell_and_select_below();
112 that.notebook.execute_cell_and_select_below();
105 }
113 }
106 },
114 },
107 {
115 {
108 id : 'interrupt_b',
116 id : 'interrupt_b',
109 label : 'Interrupt',
117 label : 'Interrupt',
110 icon : 'icon-stop',
118 icon : 'fa-stop',
111 callback : function () {
119 callback : function () {
112 IPython.notebook.session.interrupt_kernel();
120 that.notebook.session.interrupt_kernel();
113 }
121 }
114 },
122 },
115 {
123 {
116 id : 'repeat_b',
124 id : 'repeat_b',
117 label : 'Restart Kernel',
125 label : 'Restart Kernel',
118 icon : 'icon-repeat',
126 icon : 'fa-repeat',
119 callback : function () {
127 callback : function () {
120 IPython.notebook.restart_kernel();
128 that.notebook.restart_kernel();
121 }
129 }
122 }
130 }
123 ],'run_int');
131 ],'run_int');
@@ -150,30 +158,31 b' var IPython = (function (IPython) {'
150 .addClass('form-control select-xs')
158 .addClass('form-control select-xs')
151 .append($('<option/>').attr('value', '').text('None'));
159 .append($('<option/>').attr('value', '').text('None'));
152 this.element.append(label).append(select);
160 this.element.append(label).append(select);
161 var that = this;
153 select.change(function() {
162 select.change(function() {
154 var val = $(this).val()
163 var val = $(this).val();
155 if (val =='') {
164 if (val ==='') {
156 IPython.CellToolbar.global_hide();
165 celltoolbar.CellToolbar.global_hide();
157 delete IPython.notebook.metadata.celltoolbar;
166 delete that.notebook.metadata.celltoolbar;
158 } else {
167 } else {
159 IPython.CellToolbar.global_show();
168 celltoolbar.CellToolbar.global_show();
160 IPython.CellToolbar.activate_preset(val);
169 celltoolbar.CellToolbar.activate_preset(val, that.events);
161 IPython.notebook.metadata.celltoolbar = val;
170 that.notebook.metadata.celltoolbar = val;
162 }
171 }
163 });
172 });
164 // Setup the currently registered presets.
173 // Setup the currently registered presets.
165 var presets = IPython.CellToolbar.list_presets();
174 var presets = celltoolbar.CellToolbar.list_presets();
166 for (var i=0; i<presets.length; i++) {
175 for (var i=0; i<presets.length; i++) {
167 var name = presets[i];
176 var name = presets[i];
168 select.append($('<option/>').attr('value', name).text(name));
177 select.append($('<option/>').attr('value', name).text(name));
169 }
178 }
170 // Setup future preset registrations.
179 // Setup future preset registrations.
171 $([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
180 this.events.on('preset_added.CellToolbar', function (event, data) {
172 var name = data.name;
181 var name = data.name;
173 select.append($('<option/>').attr('value', name).text(name));
182 select.append($('<option/>').attr('value', name).text(name));
174 });
183 });
175 // Update select value when a preset is activated.
184 // Update select value when a preset is activated.
176 $([IPython.events]).on('preset_activated.CellToolbar', function (event, data) {
185 this.events.on('preset_activated.CellToolbar', function (event, data) {
177 if (select.val() !== data.name)
186 if (select.val() !== data.name)
178 select.val(data.name);
187 select.val(data.name);
179 });
188 });
@@ -186,26 +195,26 b' var IPython = (function (IPython) {'
186 this.element.find('#cell_type').change(function () {
195 this.element.find('#cell_type').change(function () {
187 var cell_type = $(this).val();
196 var cell_type = $(this).val();
188 if (cell_type === 'code') {
197 if (cell_type === 'code') {
189 IPython.notebook.to_code();
198 that.notebook.to_code();
190 } else if (cell_type === 'markdown') {
199 } else if (cell_type === 'markdown') {
191 IPython.notebook.to_markdown();
200 that.notebook.to_markdown();
192 } else if (cell_type === 'raw') {
201 } else if (cell_type === 'raw') {
193 IPython.notebook.to_raw();
202 that.notebook.to_raw();
194 } else if (cell_type === 'heading1') {
203 } else if (cell_type === 'heading1') {
195 IPython.notebook.to_heading(undefined, 1);
204 that.notebook.to_heading(undefined, 1);
196 } else if (cell_type === 'heading2') {
205 } else if (cell_type === 'heading2') {
197 IPython.notebook.to_heading(undefined, 2);
206 that.notebook.to_heading(undefined, 2);
198 } else if (cell_type === 'heading3') {
207 } else if (cell_type === 'heading3') {
199 IPython.notebook.to_heading(undefined, 3);
208 that.notebook.to_heading(undefined, 3);
200 } else if (cell_type === 'heading4') {
209 } else if (cell_type === 'heading4') {
201 IPython.notebook.to_heading(undefined, 4);
210 that.notebook.to_heading(undefined, 4);
202 } else if (cell_type === 'heading5') {
211 } else if (cell_type === 'heading5') {
203 IPython.notebook.to_heading(undefined, 5);
212 that.notebook.to_heading(undefined, 5);
204 } else if (cell_type === 'heading6') {
213 } else if (cell_type === 'heading6') {
205 IPython.notebook.to_heading(undefined, 6);
214 that.notebook.to_heading(undefined, 6);
206 }
215 }
207 });
216 });
208 $([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
217 this.events.on('selected_cell_type_changed.Notebook', function (event, data) {
209 if (data.cell_type === 'heading') {
218 if (data.cell_type === 'heading') {
210 that.element.find('#cell_type').val(data.cell_type+data.level);
219 that.element.find('#cell_type').val(data.cell_type+data.level);
211 } else {
220 } else {
@@ -214,8 +223,8 b' var IPython = (function (IPython) {'
214 });
223 });
215 };
224 };
216
225
226 // Backwards compatability.
217 IPython.MainToolBar = MainToolBar;
227 IPython.MainToolBar = MainToolBar;
218
228
219 return IPython;
229 return {'MainToolBar': MainToolBar};
220
230 });
221 }(IPython));
@@ -1,18 +1,12 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 'base/js/utils',
8 //============================================================================
8 'base/js/dialog',
9 // MathJax utility functions
9 ], function(IPython, $, utils, dialog) {
10 //============================================================================
11
12
13 IPython.namespace('IPython.mathjaxutils');
14
15 IPython.mathjaxutils = (function (IPython) {
16 "use strict";
10 "use strict";
17
11
18 var init = function () {
12 var init = function () {
@@ -75,7 +69,7 b' IPython.mathjaxutils = (function (IPython) {'
75 "which will prevent this dialog from appearing."
69 "which will prevent this dialog from appearing."
76 )
70 )
77 );
71 );
78 IPython.dialog.modal({
72 dialog.modal({
79 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
73 title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'",
80 body : message,
74 body : message,
81 buttons : {
75 buttons : {
@@ -110,7 +104,7 b' IPython.mathjaxutils = (function (IPython) {'
110 .replace(/</g, "&lt;") // use HTML entity for <
104 .replace(/</g, "&lt;") // use HTML entity for <
111 .replace(/>/g, "&gt;") // use HTML entity for >
105 .replace(/>/g, "&gt;") // use HTML entity for >
112 ;
106 ;
113 if (IPython.utils.browser === 'msie') {
107 if (utils.browser === 'msie') {
114 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
108 block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n");
115 }
109 }
116 while (j > i) {
110 while (j > i) {
@@ -159,7 +153,7 b' IPython.mathjaxutils = (function (IPython) {'
159 de_tilde = function (text) { return text; };
153 de_tilde = function (text) { return text; };
160 }
154 }
161
155
162 var blocks = IPython.utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
156 var blocks = utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT);
163
157
164 for (var i = 1, m = blocks.length; i < m; i += 2) {
158 for (var i = 1, m = blocks.length; i < m; i += 2) {
165 var block = blocks[i];
159 var block = blocks[i];
@@ -242,10 +236,13 b' IPython.mathjaxutils = (function (IPython) {'
242 return text;
236 return text;
243 };
237 };
244
238
245 return {
239 var mathjaxutils = {
246 init : init,
240 init : init,
247 remove_math : remove_math,
241 remove_math : remove_math,
248 replace_math : replace_math
242 replace_math : replace_math
249 };
243 };
250
244
251 }(IPython));
245 IPython.mathjaxutils = mathjaxutils;
246
247 return mathjaxutils;
248 });
@@ -1,40 +1,48 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 //============================================================================
4 define([
5 // MenuBar
5 'base/js/namespace',
6 //============================================================================
6 'jquery',
7
7 'base/js/utils',
8 /**
8 'notebook/js/tour',
9 * @module IPython
9 'bootstrap',
10 * @namespace IPython
10 ], function(IPython, $, utils, tour) {
11 * @submodule MenuBar
12 */
13
14
15 var IPython = (function (IPython) {
16 "use strict";
11 "use strict";
17
12
18 var utils = IPython.utils;
19
20 /**
21 * A MenuBar Class to generate the menubar of IPython notebook
22 * @Class MenuBar
23 *
24 * @constructor
25 *
26 *
27 * @param selector {string} selector for the menubar element in DOM
28 * @param {object} [options]
29 * @param [options.base_url] {String} String to use for the
30 * base project url. Default is to inspect
31 * $('body').data('baseUrl');
32 * does not support change for now is set through this option
33 */
34 var MenuBar = function (selector, options) {
13 var MenuBar = function (selector, options) {
14 // Constructor
15 //
16 // A MenuBar Class to generate the menubar of IPython notebook
17 //
18 // Parameters:
19 // selector: string
20 // options: dictionary
21 // Dictionary of keyword arguments.
22 // notebook: Notebook instance
23 // layout_manager: LayoutManager instance
24 // events: $(Events) instance
25 // save_widget: SaveWidget instance
26 // quick_help: QuickHelp instance
27 // base_url : string
28 // notebook_path : string
29 // notebook_name : string
35 options = options || {};
30 options = options || {};
36 this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl");
31 this.base_url = options.base_url || utils.get_body_data("baseUrl");
37 this.selector = selector;
32 this.selector = selector;
33 this.notebook = options.notebook;
34 this.layout_manager = options.layout_manager;
35 this.events = options.events;
36 this.save_widget = options.save_widget;
37 this.quick_help = options.quick_help;
38
39 try {
40 this.tour = new tour.Tour(this.notebook, this.events);
41 } catch (e) {
42 this.tour = undefined;
43 console.log("Failed to instantiate Notebook Tour", e);
44 }
45
38 if (this.selector !== undefined) {
46 if (this.selector !== undefined) {
39 this.element = $(selector);
47 this.element = $(selector);
40 this.style();
48 this.style();
@@ -42,23 +50,24 b' var IPython = (function (IPython) {'
42 }
50 }
43 };
51 };
44
52
53 // TODO: This has definitively nothing to do with style ...
45 MenuBar.prototype.style = function () {
54 MenuBar.prototype.style = function () {
46 this.element.addClass('border-box-sizing');
55 var that = this;
47 this.element.find("li").click(function (event, ui) {
56 this.element.find("li").click(function (event, ui) {
48 // The selected cell loses focus when the menu is entered, so we
57 // The selected cell loses focus when the menu is entered, so we
49 // re-select it upon selection.
58 // re-select it upon selection.
50 var i = IPython.notebook.get_selected_index();
59 var i = that.notebook.get_selected_index();
51 IPython.notebook.select(i);
60 that.notebook.select(i);
52 }
61 }
53 );
62 );
54 };
63 };
55
64
56 MenuBar.prototype._nbconvert = function (format, download) {
65 MenuBar.prototype._nbconvert = function (format, download) {
57 download = download || false;
66 download = download || false;
58 var notebook_path = IPython.notebook.notebook_path;
67 var notebook_path = this.notebook.notebook_path;
59 var notebook_name = IPython.notebook.notebook_name;
68 var notebook_name = this.notebook.notebook_name;
60 if (IPython.notebook.dirty) {
69 if (this.notebook.dirty) {
61 IPython.notebook.save_notebook({async : false});
70 this.notebook.save_notebook({async : false});
62 }
71 }
63 var url = utils.url_join_encode(
72 var url = utils.url_join_encode(
64 this.base_url,
73 this.base_url,
@@ -75,25 +84,25 b' var IPython = (function (IPython) {'
75 // File
84 // File
76 var that = this;
85 var that = this;
77 this.element.find('#new_notebook').click(function () {
86 this.element.find('#new_notebook').click(function () {
78 IPython.notebook.new_notebook();
87 that.notebook.new_notebook();
79 });
88 });
80 this.element.find('#open_notebook').click(function () {
89 this.element.find('#open_notebook').click(function () {
81 window.open(utils.url_join_encode(
90 window.open(utils.url_join_encode(
82 IPython.notebook.base_url,
91 that.notebook.base_url,
83 'tree',
92 'tree',
84 IPython.notebook.notebook_path
93 that.notebook.notebook_path
85 ));
94 ));
86 });
95 });
87 this.element.find('#copy_notebook').click(function () {
96 this.element.find('#copy_notebook').click(function () {
88 IPython.notebook.copy_notebook();
97 that.notebook.copy_notebook();
89 return false;
98 return false;
90 });
99 });
91 this.element.find('#download_ipynb').click(function () {
100 this.element.find('#download_ipynb').click(function () {
92 var base_url = IPython.notebook.base_url;
101 var base_url = that.notebook.base_url;
93 var notebook_path = IPython.notebook.notebook_path;
102 var notebook_path = that.notebook.notebook_path;
94 var notebook_name = IPython.notebook.notebook_name;
103 var notebook_name = that.notebook.notebook_name;
95 if (IPython.notebook.dirty) {
104 if (that.notebook.dirty) {
96 IPython.notebook.save_notebook({async : false});
105 that.notebook.save_notebook({async : false});
97 }
106 }
98
107
99 var url = utils.url_join_encode(
108 var url = utils.url_join_encode(
@@ -126,17 +135,17 b' var IPython = (function (IPython) {'
126 });
135 });
127
136
128 this.element.find('#rename_notebook').click(function () {
137 this.element.find('#rename_notebook').click(function () {
129 IPython.save_widget.rename_notebook();
138 that.save_widget.rename_notebook({notebook: that.notebook});
130 });
139 });
131 this.element.find('#save_checkpoint').click(function () {
140 this.element.find('#save_checkpoint').click(function () {
132 IPython.notebook.save_checkpoint();
141 that.notebook.save_checkpoint();
133 });
142 });
134 this.element.find('#restore_checkpoint').click(function () {
143 this.element.find('#restore_checkpoint').click(function () {
135 });
144 });
136 this.element.find('#trust_notebook').click(function () {
145 this.element.find('#trust_notebook').click(function () {
137 IPython.notebook.trust_notebook();
146 that.notebook.trust_notebook();
138 });
147 });
139 $([IPython.events]).on('trust_changed.Notebook', function (event, trusted) {
148 this.events.on('trust_changed.Notebook', function (event, trusted) {
140 if (trusted) {
149 if (trusted) {
141 that.element.find('#trust_notebook')
150 that.element.find('#trust_notebook')
142 .addClass("disabled")
151 .addClass("disabled")
@@ -148,7 +157,7 b' var IPython = (function (IPython) {'
148 }
157 }
149 });
158 });
150 this.element.find('#kill_and_exit').click(function () {
159 this.element.find('#kill_and_exit').click(function () {
151 IPython.notebook.session.delete();
160 that.notebook.session.delete();
152 setTimeout(function(){
161 setTimeout(function(){
153 // allow closing of new tabs in Chromium, impossible in FF
162 // allow closing of new tabs in Chromium, impossible in FF
154 window.open('', '_self', '');
163 window.open('', '_self', '');
@@ -157,148 +166,150 b' var IPython = (function (IPython) {'
157 });
166 });
158 // Edit
167 // Edit
159 this.element.find('#cut_cell').click(function () {
168 this.element.find('#cut_cell').click(function () {
160 IPython.notebook.cut_cell();
169 that.notebook.cut_cell();
161 });
170 });
162 this.element.find('#copy_cell').click(function () {
171 this.element.find('#copy_cell').click(function () {
163 IPython.notebook.copy_cell();
172 that.notebook.copy_cell();
164 });
173 });
165 this.element.find('#delete_cell').click(function () {
174 this.element.find('#delete_cell').click(function () {
166 IPython.notebook.delete_cell();
175 that.notebook.delete_cell();
167 });
176 });
168 this.element.find('#undelete_cell').click(function () {
177 this.element.find('#undelete_cell').click(function () {
169 IPython.notebook.undelete_cell();
178 that.notebook.undelete_cell();
170 });
179 });
171 this.element.find('#split_cell').click(function () {
180 this.element.find('#split_cell').click(function () {
172 IPython.notebook.split_cell();
181 that.notebook.split_cell();
173 });
182 });
174 this.element.find('#merge_cell_above').click(function () {
183 this.element.find('#merge_cell_above').click(function () {
175 IPython.notebook.merge_cell_above();
184 that.notebook.merge_cell_above();
176 });
185 });
177 this.element.find('#merge_cell_below').click(function () {
186 this.element.find('#merge_cell_below').click(function () {
178 IPython.notebook.merge_cell_below();
187 that.notebook.merge_cell_below();
179 });
188 });
180 this.element.find('#move_cell_up').click(function () {
189 this.element.find('#move_cell_up').click(function () {
181 IPython.notebook.move_cell_up();
190 that.notebook.move_cell_up();
182 });
191 });
183 this.element.find('#move_cell_down').click(function () {
192 this.element.find('#move_cell_down').click(function () {
184 IPython.notebook.move_cell_down();
193 that.notebook.move_cell_down();
185 });
194 });
186 this.element.find('#edit_nb_metadata').click(function () {
195 this.element.find('#edit_nb_metadata').click(function () {
187 IPython.notebook.edit_metadata();
196 that.notebook.edit_metadata({
197 notebook: that.notebook,
198 keyboard_manager: that.notebook.keyboard_manager});
188 });
199 });
189
200
190 // View
201 // View
191 this.element.find('#toggle_header').click(function () {
202 this.element.find('#toggle_header').click(function () {
192 $('div#header').toggle();
203 $('div#header').toggle();
193 IPython.layout_manager.do_resize();
204 that.layout_manager.do_resize();
194 });
205 });
195 this.element.find('#toggle_toolbar').click(function () {
206 this.element.find('#toggle_toolbar').click(function () {
196 $('div#maintoolbar').toggle();
207 $('div#maintoolbar').toggle();
197 IPython.layout_manager.do_resize();
208 that.layout_manager.do_resize();
198 });
209 });
199 // Insert
210 // Insert
200 this.element.find('#insert_cell_above').click(function () {
211 this.element.find('#insert_cell_above').click(function () {
201 IPython.notebook.insert_cell_above('code');
212 that.notebook.insert_cell_above('code');
202 IPython.notebook.select_prev();
213 that.notebook.select_prev();
203 });
214 });
204 this.element.find('#insert_cell_below').click(function () {
215 this.element.find('#insert_cell_below').click(function () {
205 IPython.notebook.insert_cell_below('code');
216 that.notebook.insert_cell_below('code');
206 IPython.notebook.select_next();
217 that.notebook.select_next();
207 });
218 });
208 // Cell
219 // Cell
209 this.element.find('#run_cell').click(function () {
220 this.element.find('#run_cell').click(function () {
210 IPython.notebook.execute_cell();
221 that.notebook.execute_cell();
211 });
222 });
212 this.element.find('#run_cell_select_below').click(function () {
223 this.element.find('#run_cell_select_below').click(function () {
213 IPython.notebook.execute_cell_and_select_below();
224 that.notebook.execute_cell_and_select_below();
214 });
225 });
215 this.element.find('#run_cell_insert_below').click(function () {
226 this.element.find('#run_cell_insert_below').click(function () {
216 IPython.notebook.execute_cell_and_insert_below();
227 that.notebook.execute_cell_and_insert_below();
217 });
228 });
218 this.element.find('#run_all_cells').click(function () {
229 this.element.find('#run_all_cells').click(function () {
219 IPython.notebook.execute_all_cells();
230 that.notebook.execute_all_cells();
220 });
231 });
221 this.element.find('#run_all_cells_above').click(function () {
232 this.element.find('#run_all_cells_above').click(function () {
222 IPython.notebook.execute_cells_above();
233 that.notebook.execute_cells_above();
223 });
234 });
224 this.element.find('#run_all_cells_below').click(function () {
235 this.element.find('#run_all_cells_below').click(function () {
225 IPython.notebook.execute_cells_below();
236 that.notebook.execute_cells_below();
226 });
237 });
227 this.element.find('#to_code').click(function () {
238 this.element.find('#to_code').click(function () {
228 IPython.notebook.to_code();
239 that.notebook.to_code();
229 });
240 });
230 this.element.find('#to_markdown').click(function () {
241 this.element.find('#to_markdown').click(function () {
231 IPython.notebook.to_markdown();
242 that.notebook.to_markdown();
232 });
243 });
233 this.element.find('#to_raw').click(function () {
244 this.element.find('#to_raw').click(function () {
234 IPython.notebook.to_raw();
245 that.notebook.to_raw();
235 });
246 });
236 this.element.find('#to_heading1').click(function () {
247 this.element.find('#to_heading1').click(function () {
237 IPython.notebook.to_heading(undefined, 1);
248 that.notebook.to_heading(undefined, 1);
238 });
249 });
239 this.element.find('#to_heading2').click(function () {
250 this.element.find('#to_heading2').click(function () {
240 IPython.notebook.to_heading(undefined, 2);
251 that.notebook.to_heading(undefined, 2);
241 });
252 });
242 this.element.find('#to_heading3').click(function () {
253 this.element.find('#to_heading3').click(function () {
243 IPython.notebook.to_heading(undefined, 3);
254 that.notebook.to_heading(undefined, 3);
244 });
255 });
245 this.element.find('#to_heading4').click(function () {
256 this.element.find('#to_heading4').click(function () {
246 IPython.notebook.to_heading(undefined, 4);
257 that.notebook.to_heading(undefined, 4);
247 });
258 });
248 this.element.find('#to_heading5').click(function () {
259 this.element.find('#to_heading5').click(function () {
249 IPython.notebook.to_heading(undefined, 5);
260 that.notebook.to_heading(undefined, 5);
250 });
261 });
251 this.element.find('#to_heading6').click(function () {
262 this.element.find('#to_heading6').click(function () {
252 IPython.notebook.to_heading(undefined, 6);
263 that.notebook.to_heading(undefined, 6);
253 });
264 });
254
265
255 this.element.find('#toggle_current_output').click(function () {
266 this.element.find('#toggle_current_output').click(function () {
256 IPython.notebook.toggle_output();
267 that.notebook.toggle_output();
257 });
268 });
258 this.element.find('#toggle_current_output_scroll').click(function () {
269 this.element.find('#toggle_current_output_scroll').click(function () {
259 IPython.notebook.toggle_output_scroll();
270 that.notebook.toggle_output_scroll();
260 });
271 });
261 this.element.find('#clear_current_output').click(function () {
272 this.element.find('#clear_current_output').click(function () {
262 IPython.notebook.clear_output();
273 that.notebook.clear_output();
263 });
274 });
264
275
265 this.element.find('#toggle_all_output').click(function () {
276 this.element.find('#toggle_all_output').click(function () {
266 IPython.notebook.toggle_all_output();
277 that.notebook.toggle_all_output();
267 });
278 });
268 this.element.find('#toggle_all_output_scroll').click(function () {
279 this.element.find('#toggle_all_output_scroll').click(function () {
269 IPython.notebook.toggle_all_output_scroll();
280 that.notebook.toggle_all_output_scroll();
270 });
281 });
271 this.element.find('#clear_all_output').click(function () {
282 this.element.find('#clear_all_output').click(function () {
272 IPython.notebook.clear_all_output();
283 that.notebook.clear_all_output();
273 });
284 });
274
285
275 // Kernel
286 // Kernel
276 this.element.find('#int_kernel').click(function () {
287 this.element.find('#int_kernel').click(function () {
277 IPython.notebook.session.interrupt_kernel();
288 that.notebook.session.interrupt_kernel();
278 });
289 });
279 this.element.find('#restart_kernel').click(function () {
290 this.element.find('#restart_kernel').click(function () {
280 IPython.notebook.restart_kernel();
291 that.notebook.restart_kernel();
281 });
292 });
282 // Help
293 // Help
283 if (IPython.tour) {
294 if (this.tour) {
284 this.element.find('#notebook_tour').click(function () {
295 this.element.find('#notebook_tour').click(function () {
285 IPython.tour.start();
296 that.tour.start();
286 });
297 });
287 } else {
298 } else {
288 this.element.find('#notebook_tour').addClass("disabled");
299 this.element.find('#notebook_tour').addClass("disabled");
289 }
300 }
290 this.element.find('#keyboard_shortcuts').click(function () {
301 this.element.find('#keyboard_shortcuts').click(function () {
291 IPython.quick_help.show_keyboard_shortcuts();
302 that.quick_help.show_keyboard_shortcuts();
292 });
303 });
293
304
294 this.update_restore_checkpoint(null);
305 this.update_restore_checkpoint(null);
295
306
296 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
307 this.events.on('checkpoints_listed.Notebook', function (event, data) {
297 that.update_restore_checkpoint(IPython.notebook.checkpoints);
308 that.update_restore_checkpoint(that.notebook.checkpoints);
298 });
309 });
299
310
300 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
311 this.events.on('checkpoint_created.Notebook', function (event, data) {
301 that.update_restore_checkpoint(IPython.notebook.checkpoints);
312 that.update_restore_checkpoint(that.notebook.checkpoints);
302 });
313 });
303 };
314 };
304
315
@@ -317,6 +328,7 b' var IPython = (function (IPython) {'
317 return;
328 return;
318 }
329 }
319
330
331 var that = this;
320 checkpoints.map(function (checkpoint) {
332 checkpoints.map(function (checkpoint) {
321 var d = new Date(checkpoint.last_modified);
333 var d = new Date(checkpoint.last_modified);
322 ul.append(
334 ul.append(
@@ -325,15 +337,15 b' var IPython = (function (IPython) {'
325 .attr("href", "#")
337 .attr("href", "#")
326 .text(d.format("mmm dd HH:MM:ss"))
338 .text(d.format("mmm dd HH:MM:ss"))
327 .click(function () {
339 .click(function () {
328 IPython.notebook.restore_checkpoint_dialog(checkpoint);
340 that.notebook.restore_checkpoint_dialog(checkpoint);
329 })
341 })
330 )
342 )
331 );
343 );
332 });
344 });
333 };
345 };
334
346
347 // Backwards compatability.
335 IPython.MenuBar = MenuBar;
348 IPython.MenuBar = MenuBar;
336
349
337 return IPython;
350 return {'MenuBar': MenuBar};
338
351 });
339 }(IPython));
@@ -1,32 +1,98 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7 'base/js/utils',
8 'base/js/dialog',
9 'notebook/js/textcell',
10 'notebook/js/codecell',
11 'services/sessions/js/session',
12 'notebook/js/celltoolbar',
13 'components/marked/lib/marked',
14 'highlight',
15 'notebook/js/mathjaxutils',
16 'base/js/keyboard',
17 'notebook/js/tooltip',
18 'notebook/js/celltoolbarpresets/default',
19 'notebook/js/celltoolbarpresets/rawcell',
20 'notebook/js/celltoolbarpresets/slideshow',
21 ], function (
22 IPython,
23 $,
24 utils,
25 dialog,
26 textcell,
27 codecell,
28 session,
29 celltoolbar,
30 marked,
31 hljs,
32 mathjaxutils,
33 keyboard,
34 tooltip,
35 default_celltoolbar,
36 rawcell_celltoolbar,
37 slideshow_celltoolbar
38 ) {
7
39
8 //============================================================================
9 // Notebook
10 //============================================================================
11
12 var IPython = (function (IPython) {
13 "use strict";
14
15 var utils = IPython.utils;
16
17 /**
18 * A notebook contains and manages cells.
19 *
20 * @class Notebook
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
24 */
25 var Notebook = function (selector, options) {
40 var Notebook = function (selector, options) {
26 this.options = options = options || {};
41 // Constructor
42 //
43 // A notebook contains and manages cells.
44 //
45 // Parameters:
46 // selector: string
47 // options: dictionary
48 // Dictionary of keyword arguments.
49 // events: $(Events) instance
50 // keyboard_manager: KeyboardManager instance
51 // save_widget: SaveWidget instance
52 // config: dictionary
53 // base_url : string
54 // notebook_path : string
55 // notebook_name : string
56 this.config = options.config || {};
27 this.base_url = options.base_url;
57 this.base_url = options.base_url;
28 this.notebook_path = options.notebook_path;
58 this.notebook_path = options.notebook_path;
29 this.notebook_name = options.notebook_name;
59 this.notebook_name = options.notebook_name;
60 this.events = options.events;
61 this.keyboard_manager = options.keyboard_manager;
62 this.save_widget = options.save_widget;
63 this.tooltip = new tooltip.Tooltip(this.events);
64 this.ws_url = options.ws_url;
65 // default_kernel_name is a temporary measure while we implement proper
66 // kernel selection and delayed start. Do not rely on it.
67 this.default_kernel_name = 'python';
68 // TODO: This code smells (and the other `= this` line a couple lines down)
69 // We need a better way to deal with circular instance references.
70 this.keyboard_manager.notebook = this;
71 this.save_widget.notebook = this;
72
73 mathjaxutils.init();
74
75 if (marked) {
76 marked.setOptions({
77 gfm : true,
78 tables: true,
79 langPrefix: "language-",
80 highlight: function(code, lang) {
81 if (!lang) {
82 // no language, no highlight
83 return code;
84 }
85 var highlighted;
86 try {
87 highlighted = hljs.highlight(lang, code, false);
88 } catch(err) {
89 highlighted = hljs.highlightAuto(code);
90 }
91 return highlighted.value;
92 }
93 });
94 }
95
30 this.element = $(selector);
96 this.element = $(selector);
31 this.element.scroll();
97 this.element.scroll();
32 this.element.data("notebook", this);
98 this.element.data("notebook", this);
@@ -55,23 +121,20 b' var IPython = (function (IPython) {'
55 this.notebook_name_blacklist_re = /[\/\\:]/;
121 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3; // Increment this when changing the nbformat
122 this.nbformat = 3; // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
123 this.nbformat_minor = 0; // Increment this when changing the nbformat
58 this.style();
124 this.codemirror_mode = 'ipython';
59 this.create_elements();
125 this.create_elements();
60 this.bind_events();
126 this.bind_events();
61 this.save_notebook = function() { // don't allow save until notebook_loaded
127 this.save_notebook = function() { // don't allow save until notebook_loaded
62 this.save_notebook_error(null, null, "Load failed, save is disabled");
128 this.save_notebook_error(null, null, "Load failed, save is disabled");
63 };
129 };
64 };
65
130
66 /**
131 // Trigger cell toolbar registration.
67 * Tweak the notebook's CSS style.
132 default_celltoolbar.register(this);
68 *
133 rawcell_celltoolbar.register(this);
69 * @method style
134 slideshow_celltoolbar.register(this);
70 */
71 Notebook.prototype.style = function () {
72 $('div#notebook').addClass('border-box-sizing');
73 };
135 };
74
136
137
75 /**
138 /**
76 * Create an HTML and CSS representation of the notebook.
139 * Create an HTML and CSS representation of the notebook.
77 *
140 *
@@ -102,36 +165,38 b' var IPython = (function (IPython) {'
102 Notebook.prototype.bind_events = function () {
165 Notebook.prototype.bind_events = function () {
103 var that = this;
166 var that = this;
104
167
105 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
168 this.events.on('set_next_input.Notebook', function (event, data) {
106 var index = that.find_cell_index(data.cell);
169 var index = that.find_cell_index(data.cell);
107 var new_cell = that.insert_cell_below('code',index);
170 var new_cell = that.insert_cell_below('code',index);
108 new_cell.set_text(data.text);
171 new_cell.set_text(data.text);
109 that.dirty = true;
172 that.dirty = true;
110 });
173 });
111
174
112 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
175 this.events.on('set_dirty.Notebook', function (event, data) {
113 that.dirty = data.value;
176 that.dirty = data.value;
114 });
177 });
115
178
116 $([IPython.events]).on('trust_changed.Notebook', function (event, data) {
179 this.events.on('trust_changed.Notebook', function (event, data) {
117 that.trusted = data.value;
180 that.trusted = data.value;
118 });
181 });
119
182
120 $([IPython.events]).on('select.Cell', function (event, data) {
183 this.events.on('select.Cell', function (event, data) {
121 var index = that.find_cell_index(data.cell);
184 var index = that.find_cell_index(data.cell);
122 that.select(index);
185 that.select(index);
123 });
186 });
124
187
125 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
188 this.events.on('edit_mode.Cell', function (event, data) {
126 that.handle_edit_mode(data.cell);
189 that.handle_edit_mode(data.cell);
127 });
190 });
128
191
129 $([IPython.events]).on('command_mode.Cell', function (event, data) {
192 this.events.on('command_mode.Cell', function (event, data) {
130 that.handle_command_mode(data.cell);
193 that.handle_command_mode(data.cell);
131 });
194 });
132
195
133 $([IPython.events]).on('status_autorestarting.Kernel', function () {
196 this.events.on('status_autorestarting.Kernel', function () {
134 IPython.dialog.modal({
197 dialog.modal({
198 notebook: that,
199 keyboard_manager: that.keyboard_manager,
135 title: "Kernel Restarting",
200 title: "Kernel Restarting",
136 body: "The kernel appears to have died. It will restart automatically.",
201 body: "The kernel appears to have died. It will restart automatically.",
137 buttons: {
202 buttons: {
@@ -141,6 +206,13 b' var IPython = (function (IPython) {'
141 }
206 }
142 });
207 });
143 });
208 });
209
210 this.events.on('spec_changed.Kernel', function(event, data) {
211 that.set_kernelspec_metadata(data);
212 if (data.codemirror_mode) {
213 that.set_codemirror_mode(data.codemirror_mode);
214 }
215 });
144
216
145 var collapse_time = function (time) {
217 var collapse_time = function (time) {
146 var app_height = $('#ipython-main-app').height(); // content height
218 var app_height = $('#ipython-main-app').height(); // content height
@@ -211,7 +283,7 b' var IPython = (function (IPython) {'
211 if (this.dirty == value) {
283 if (this.dirty == value) {
212 return;
284 return;
213 }
285 }
214 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
286 this.events.trigger('set_dirty.Notebook', {value: value});
215 };
287 };
216
288
217 /**
289 /**
@@ -254,10 +326,25 b' var IPython = (function (IPython) {'
254
326
255 Notebook.prototype.edit_metadata = function () {
327 Notebook.prototype.edit_metadata = function () {
256 var that = this;
328 var that = this;
257 IPython.dialog.edit_metadata(this.metadata, function (md) {
329 dialog.edit_metadata({
258 that.metadata = md;
330 md: this.metadata,
259 }, 'Notebook');
331 callback: function (md) {
332 that.metadata = md;
333 },
334 name: 'Notebook',
335 notebook: this,
336 keyboard_manager: this.keyboard_manager});
260 };
337 };
338
339 Notebook.prototype.set_kernelspec_metadata = function(ks) {
340 var tostore = {};
341 $.map(ks, function(value, field) {
342 if (field !== 'argv' && field !== 'env') {
343 tostore[field] = value;
344 }
345 });
346 this.metadata.kernelspec = tostore;
347 }
261
348
262 // Cell indexing, retrieval, etc.
349 // Cell indexing, retrieval, etc.
263
350
@@ -295,7 +382,7 b' var IPython = (function (IPython) {'
295 * @return {Cell} Cell or null if no cell was found.
382 * @return {Cell} Cell or null if no cell was found.
296 */
383 */
297 Notebook.prototype.get_msg_cell = function (msg_id) {
384 Notebook.prototype.get_msg_cell = function (msg_id) {
298 return IPython.CodeCell.msg_cells[msg_id] || null;
385 return codecell.CodeCell.msg_cells[msg_id] || null;
299 };
386 };
300
387
301 /**
388 /**
@@ -474,11 +561,11 b' var IPython = (function (IPython) {'
474 var cell = this.get_cell(index);
561 var cell = this.get_cell(index);
475 cell.select();
562 cell.select();
476 if (cell.cell_type === 'heading') {
563 if (cell.cell_type === 'heading') {
477 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
564 this.events.trigger('selected_cell_type_changed.Notebook',
478 {'cell_type':cell.cell_type,level:cell.level}
565 {'cell_type':cell.cell_type,level:cell.level}
479 );
566 );
480 } else {
567 } else {
481 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
568 this.events.trigger('selected_cell_type_changed.Notebook',
482 {'cell_type':cell.cell_type}
569 {'cell_type':cell.cell_type}
483 );
570 );
484 }
571 }
@@ -540,8 +627,8 b' var IPython = (function (IPython) {'
540 if (this.mode !== 'command') {
627 if (this.mode !== 'command') {
541 cell.command_mode();
628 cell.command_mode();
542 this.mode = 'command';
629 this.mode = 'command';
543 $([IPython.events]).trigger('command_mode.Notebook');
630 this.events.trigger('command_mode.Notebook');
544 IPython.keyboard_manager.command_mode();
631 this.keyboard_manager.command_mode();
545 }
632 }
546 };
633 };
547
634
@@ -570,8 +657,8 b' var IPython = (function (IPython) {'
570 if (cell && this.mode !== 'edit') {
657 if (cell && this.mode !== 'edit') {
571 cell.edit_mode();
658 cell.edit_mode();
572 this.mode = 'edit';
659 this.mode = 'edit';
573 $([IPython.events]).trigger('edit_mode.Notebook');
660 this.events.trigger('edit_mode.Notebook');
574 IPython.keyboard_manager.edit_mode();
661 this.keyboard_manager.edit_mode();
575 }
662 }
576 };
663 };
577
664
@@ -686,7 +773,7 b' var IPython = (function (IPython) {'
686 this.undelete_index = i;
773 this.undelete_index = i;
687 this.undelete_below = false;
774 this.undelete_below = false;
688 }
775 }
689 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
776 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
690 this.set_dirty(true);
777 this.set_dirty(true);
691 }
778 }
692 return this;
779 return this;
@@ -753,20 +840,27 b' var IPython = (function (IPython) {'
753 type = type || this.get_selected_cell().cell_type;
840 type = type || this.get_selected_cell().cell_type;
754
841
755 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
842 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
843 var cell_options = {
844 events: this.events,
845 config: this.config,
846 keyboard_manager: this.keyboard_manager,
847 notebook: this,
848 tooltip: this.tooltip,
849 };
756 if (type === 'code') {
850 if (type === 'code') {
757 cell = new IPython.CodeCell(this.kernel);
851 cell = new codecell.CodeCell(this.kernel, cell_options);
758 cell.set_input_prompt();
852 cell.set_input_prompt();
759 } else if (type === 'markdown') {
853 } else if (type === 'markdown') {
760 cell = new IPython.MarkdownCell();
854 cell = new textcell.MarkdownCell(cell_options);
761 } else if (type === 'raw') {
855 } else if (type === 'raw') {
762 cell = new IPython.RawCell();
856 cell = new textcell.RawCell(cell_options);
763 } else if (type === 'heading') {
857 } else if (type === 'heading') {
764 cell = new IPython.HeadingCell();
858 cell = new textcell.HeadingCell(cell_options);
765 }
859 }
766
860
767 if(this._insert_element_at_index(cell.element,index)) {
861 if(this._insert_element_at_index(cell.element,index)) {
768 cell.render();
862 cell.render();
769 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
863 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
770 cell.refresh();
864 cell.refresh();
771 // We used to select the cell after we refresh it, but there
865 // We used to select the cell after we refresh it, but there
772 // are now cases were this method is called where select is
866 // are now cases were this method is called where select is
@@ -876,7 +970,7 b' var IPython = (function (IPython) {'
876 if (this.is_valid_cell_index(i)) {
970 if (this.is_valid_cell_index(i)) {
877 var source_element = this.get_cell_element(i);
971 var source_element = this.get_cell_element(i);
878 var source_cell = source_element.data("cell");
972 var source_cell = source_element.data("cell");
879 if (!(source_cell instanceof IPython.CodeCell)) {
973 if (!(source_cell instanceof codecell.CodeCell)) {
880 var target_cell = this.insert_cell_below('code',i);
974 var target_cell = this.insert_cell_below('code',i);
881 var text = source_cell.get_text();
975 var text = source_cell.get_text();
882 if (text === source_cell.placeholder) {
976 if (text === source_cell.placeholder) {
@@ -906,7 +1000,7 b' var IPython = (function (IPython) {'
906 if (this.is_valid_cell_index(i)) {
1000 if (this.is_valid_cell_index(i)) {
907 var source_element = this.get_cell_element(i);
1001 var source_element = this.get_cell_element(i);
908 var source_cell = source_element.data("cell");
1002 var source_cell = source_element.data("cell");
909 if (!(source_cell instanceof IPython.MarkdownCell)) {
1003 if (!(source_cell instanceof textcell.MarkdownCell)) {
910 var target_cell = this.insert_cell_below('markdown',i);
1004 var target_cell = this.insert_cell_below('markdown',i);
911 var text = source_cell.get_text();
1005 var text = source_cell.get_text();
912 if (text === source_cell.placeholder) {
1006 if (text === source_cell.placeholder) {
@@ -920,7 +1014,7 b' var IPython = (function (IPython) {'
920 target_cell.code_mirror.clearHistory();
1014 target_cell.code_mirror.clearHistory();
921 source_element.remove();
1015 source_element.remove();
922 this.select(i);
1016 this.select(i);
923 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
1017 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
924 target_cell.render();
1018 target_cell.render();
925 }
1019 }
926 var cursor = source_cell.code_mirror.getCursor();
1020 var cursor = source_cell.code_mirror.getCursor();
@@ -942,7 +1036,7 b' var IPython = (function (IPython) {'
942 var source_element = this.get_cell_element(i);
1036 var source_element = this.get_cell_element(i);
943 var source_cell = source_element.data("cell");
1037 var source_cell = source_element.data("cell");
944 var target_cell = null;
1038 var target_cell = null;
945 if (!(source_cell instanceof IPython.RawCell)) {
1039 if (!(source_cell instanceof textcell.RawCell)) {
946 target_cell = this.insert_cell_below('raw',i);
1040 target_cell = this.insert_cell_below('raw',i);
947 var text = source_cell.get_text();
1041 var text = source_cell.get_text();
948 if (text === source_cell.placeholder) {
1042 if (text === source_cell.placeholder) {
@@ -977,7 +1071,7 b' var IPython = (function (IPython) {'
977 var source_element = this.get_cell_element(i);
1071 var source_element = this.get_cell_element(i);
978 var source_cell = source_element.data("cell");
1072 var source_cell = source_element.data("cell");
979 var target_cell = null;
1073 var target_cell = null;
980 if (source_cell instanceof IPython.HeadingCell) {
1074 if (source_cell instanceof textcell.HeadingCell) {
981 source_cell.set_level(level);
1075 source_cell.set_level(level);
982 } else {
1076 } else {
983 target_cell = this.insert_cell_below('heading',i);
1077 target_cell = this.insert_cell_below('heading',i);
@@ -996,12 +1090,12 b' var IPython = (function (IPython) {'
996 this.select(i);
1090 this.select(i);
997 var cursor = source_cell.code_mirror.getCursor();
1091 var cursor = source_cell.code_mirror.getCursor();
998 target_cell.code_mirror.setCursor(cursor);
1092 target_cell.code_mirror.setCursor(cursor);
999 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
1093 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1000 target_cell.render();
1094 target_cell.render();
1001 }
1095 }
1002 }
1096 }
1003 this.set_dirty(true);
1097 this.set_dirty(true);
1004 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1098 this.events.trigger('selected_cell_type_changed.Notebook',
1005 {'cell_type':'heading',level:level}
1099 {'cell_type':'heading',level:level}
1006 );
1100 );
1007 }
1101 }
@@ -1115,26 +1209,17 b' var IPython = (function (IPython) {'
1115 * @method split_cell
1209 * @method split_cell
1116 */
1210 */
1117 Notebook.prototype.split_cell = function () {
1211 Notebook.prototype.split_cell = function () {
1118 var mdc = IPython.MarkdownCell;
1212 var mdc = textcell.MarkdownCell;
1119 var rc = IPython.RawCell;
1213 var rc = textcell.RawCell;
1120 var cell = this.get_selected_cell();
1214 var cell = this.get_selected_cell();
1121 if (cell.is_splittable()) {
1215 if (cell.is_splittable()) {
1122 var texta = cell.get_pre_cursor();
1216 var texta = cell.get_pre_cursor();
1123 var textb = cell.get_post_cursor();
1217 var textb = cell.get_post_cursor();
1124 if (cell instanceof IPython.CodeCell) {
1218 cell.set_text(textb);
1125 // In this case the operations keep the notebook in its existing mode
1219 var new_cell = this.insert_cell_above(cell.cell_type);
1126 // so we don't need to do any post-op mode changes.
1220 // Unrender the new cell so we can call set_text.
1127 cell.set_text(textb);
1221 new_cell.unrender();
1128 var new_cell = this.insert_cell_above('code');
1222 new_cell.set_text(texta);
1129 new_cell.set_text(texta);
1130 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1131 // We know cell is !rendered so we can use set_text.
1132 cell.set_text(textb);
1133 var new_cell = this.insert_cell_above(cell.cell_type);
1134 // Unrender the new cell so we can call set_text.
1135 new_cell.unrender();
1136 new_cell.set_text(texta);
1137 }
1138 }
1223 }
1139 };
1224 };
1140
1225
@@ -1144,8 +1229,8 b' var IPython = (function (IPython) {'
1144 * @method merge_cell_above
1229 * @method merge_cell_above
1145 */
1230 */
1146 Notebook.prototype.merge_cell_above = function () {
1231 Notebook.prototype.merge_cell_above = function () {
1147 var mdc = IPython.MarkdownCell;
1232 var mdc = textcell.MarkdownCell;
1148 var rc = IPython.RawCell;
1233 var rc = textcell.RawCell;
1149 var index = this.get_selected_index();
1234 var index = this.get_selected_index();
1150 var cell = this.get_cell(index);
1235 var cell = this.get_cell(index);
1151 var render = cell.rendered;
1236 var render = cell.rendered;
@@ -1159,9 +1244,9 b' var IPython = (function (IPython) {'
1159 }
1244 }
1160 var upper_text = upper_cell.get_text();
1245 var upper_text = upper_cell.get_text();
1161 var text = cell.get_text();
1246 var text = cell.get_text();
1162 if (cell instanceof IPython.CodeCell) {
1247 if (cell instanceof codecell.CodeCell) {
1163 cell.set_text(upper_text+'\n'+text);
1248 cell.set_text(upper_text+'\n'+text);
1164 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1249 } else {
1165 cell.unrender(); // Must unrender before we set_text.
1250 cell.unrender(); // Must unrender before we set_text.
1166 cell.set_text(upper_text+'\n\n'+text);
1251 cell.set_text(upper_text+'\n\n'+text);
1167 if (render) {
1252 if (render) {
@@ -1181,8 +1266,8 b' var IPython = (function (IPython) {'
1181 * @method merge_cell_below
1266 * @method merge_cell_below
1182 */
1267 */
1183 Notebook.prototype.merge_cell_below = function () {
1268 Notebook.prototype.merge_cell_below = function () {
1184 var mdc = IPython.MarkdownCell;
1269 var mdc = textcell.MarkdownCell;
1185 var rc = IPython.RawCell;
1270 var rc = textcell.RawCell;
1186 var index = this.get_selected_index();
1271 var index = this.get_selected_index();
1187 var cell = this.get_cell(index);
1272 var cell = this.get_cell(index);
1188 var render = cell.rendered;
1273 var render = cell.rendered;
@@ -1196,9 +1281,9 b' var IPython = (function (IPython) {'
1196 }
1281 }
1197 var lower_text = lower_cell.get_text();
1282 var lower_text = lower_cell.get_text();
1198 var text = cell.get_text();
1283 var text = cell.get_text();
1199 if (cell instanceof IPython.CodeCell) {
1284 if (cell instanceof codecell.CodeCell) {
1200 cell.set_text(text+'\n'+lower_text);
1285 cell.set_text(text+'\n'+lower_text);
1201 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1286 } else {
1202 cell.unrender(); // Must unrender before we set_text.
1287 cell.unrender(); // Must unrender before we set_text.
1203 cell.set_text(text+'\n\n'+lower_text);
1288 cell.set_text(text+'\n\n'+lower_text);
1204 if (render) {
1289 if (render) {
@@ -1224,7 +1309,7 b' var IPython = (function (IPython) {'
1224 Notebook.prototype.collapse_output = function (index) {
1309 Notebook.prototype.collapse_output = function (index) {
1225 var i = this.index_or_selected(index);
1310 var i = this.index_or_selected(index);
1226 var cell = this.get_cell(i);
1311 var cell = this.get_cell(i);
1227 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1312 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1228 cell.collapse_output();
1313 cell.collapse_output();
1229 this.set_dirty(true);
1314 this.set_dirty(true);
1230 }
1315 }
@@ -1237,7 +1322,7 b' var IPython = (function (IPython) {'
1237 */
1322 */
1238 Notebook.prototype.collapse_all_output = function () {
1323 Notebook.prototype.collapse_all_output = function () {
1239 $.map(this.get_cells(), function (cell, i) {
1324 $.map(this.get_cells(), function (cell, i) {
1240 if (cell instanceof IPython.CodeCell) {
1325 if (cell instanceof codecell.CodeCell) {
1241 cell.collapse_output();
1326 cell.collapse_output();
1242 }
1327 }
1243 });
1328 });
@@ -1254,7 +1339,7 b' var IPython = (function (IPython) {'
1254 Notebook.prototype.expand_output = function (index) {
1339 Notebook.prototype.expand_output = function (index) {
1255 var i = this.index_or_selected(index);
1340 var i = this.index_or_selected(index);
1256 var cell = this.get_cell(i);
1341 var cell = this.get_cell(i);
1257 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1342 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1258 cell.expand_output();
1343 cell.expand_output();
1259 this.set_dirty(true);
1344 this.set_dirty(true);
1260 }
1345 }
@@ -1267,7 +1352,7 b' var IPython = (function (IPython) {'
1267 */
1352 */
1268 Notebook.prototype.expand_all_output = function () {
1353 Notebook.prototype.expand_all_output = function () {
1269 $.map(this.get_cells(), function (cell, i) {
1354 $.map(this.get_cells(), function (cell, i) {
1270 if (cell instanceof IPython.CodeCell) {
1355 if (cell instanceof codecell.CodeCell) {
1271 cell.expand_output();
1356 cell.expand_output();
1272 }
1357 }
1273 });
1358 });
@@ -1284,7 +1369,7 b' var IPython = (function (IPython) {'
1284 Notebook.prototype.clear_output = function (index) {
1369 Notebook.prototype.clear_output = function (index) {
1285 var i = this.index_or_selected(index);
1370 var i = this.index_or_selected(index);
1286 var cell = this.get_cell(i);
1371 var cell = this.get_cell(i);
1287 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1372 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1288 cell.clear_output();
1373 cell.clear_output();
1289 this.set_dirty(true);
1374 this.set_dirty(true);
1290 }
1375 }
@@ -1297,7 +1382,7 b' var IPython = (function (IPython) {'
1297 */
1382 */
1298 Notebook.prototype.clear_all_output = function () {
1383 Notebook.prototype.clear_all_output = function () {
1299 $.map(this.get_cells(), function (cell, i) {
1384 $.map(this.get_cells(), function (cell, i) {
1300 if (cell instanceof IPython.CodeCell) {
1385 if (cell instanceof codecell.CodeCell) {
1301 cell.clear_output();
1386 cell.clear_output();
1302 }
1387 }
1303 });
1388 });
@@ -1313,7 +1398,7 b' var IPython = (function (IPython) {'
1313 Notebook.prototype.scroll_output = function (index) {
1398 Notebook.prototype.scroll_output = function (index) {
1314 var i = this.index_or_selected(index);
1399 var i = this.index_or_selected(index);
1315 var cell = this.get_cell(i);
1400 var cell = this.get_cell(i);
1316 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1401 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1317 cell.scroll_output();
1402 cell.scroll_output();
1318 this.set_dirty(true);
1403 this.set_dirty(true);
1319 }
1404 }
@@ -1326,7 +1411,7 b' var IPython = (function (IPython) {'
1326 */
1411 */
1327 Notebook.prototype.scroll_all_output = function () {
1412 Notebook.prototype.scroll_all_output = function () {
1328 $.map(this.get_cells(), function (cell, i) {
1413 $.map(this.get_cells(), function (cell, i) {
1329 if (cell instanceof IPython.CodeCell) {
1414 if (cell instanceof codecell.CodeCell) {
1330 cell.scroll_output();
1415 cell.scroll_output();
1331 }
1416 }
1332 });
1417 });
@@ -1342,7 +1427,7 b' var IPython = (function (IPython) {'
1342 Notebook.prototype.toggle_output = function (index) {
1427 Notebook.prototype.toggle_output = function (index) {
1343 var i = this.index_or_selected(index);
1428 var i = this.index_or_selected(index);
1344 var cell = this.get_cell(i);
1429 var cell = this.get_cell(i);
1345 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1430 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1346 cell.toggle_output();
1431 cell.toggle_output();
1347 this.set_dirty(true);
1432 this.set_dirty(true);
1348 }
1433 }
@@ -1355,7 +1440,7 b' var IPython = (function (IPython) {'
1355 */
1440 */
1356 Notebook.prototype.toggle_all_output = function () {
1441 Notebook.prototype.toggle_all_output = function () {
1357 $.map(this.get_cells(), function (cell, i) {
1442 $.map(this.get_cells(), function (cell, i) {
1358 if (cell instanceof IPython.CodeCell) {
1443 if (cell instanceof codecell.CodeCell) {
1359 cell.toggle_output();
1444 cell.toggle_output();
1360 }
1445 }
1361 });
1446 });
@@ -1372,7 +1457,7 b' var IPython = (function (IPython) {'
1372 Notebook.prototype.toggle_output_scroll = function (index) {
1457 Notebook.prototype.toggle_output_scroll = function (index) {
1373 var i = this.index_or_selected(index);
1458 var i = this.index_or_selected(index);
1374 var cell = this.get_cell(i);
1459 var cell = this.get_cell(i);
1375 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1460 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1376 cell.toggle_output_scroll();
1461 cell.toggle_output_scroll();
1377 this.set_dirty(true);
1462 this.set_dirty(true);
1378 }
1463 }
@@ -1385,7 +1470,7 b' var IPython = (function (IPython) {'
1385 */
1470 */
1386 Notebook.prototype.toggle_all_output_scroll = function () {
1471 Notebook.prototype.toggle_all_output_scroll = function () {
1387 $.map(this.get_cells(), function (cell, i) {
1472 $.map(this.get_cells(), function (cell, i) {
1388 if (cell instanceof IPython.CodeCell) {
1473 if (cell instanceof codecell.CodeCell) {
1389 cell.toggle_output_scroll();
1474 cell.toggle_output_scroll();
1390 }
1475 }
1391 });
1476 });
@@ -1403,6 +1488,34 b' var IPython = (function (IPython) {'
1403 Notebook.prototype.cell_toggle_line_numbers = function() {
1488 Notebook.prototype.cell_toggle_line_numbers = function() {
1404 this.get_selected_cell().toggle_line_numbers();
1489 this.get_selected_cell().toggle_line_numbers();
1405 };
1490 };
1491
1492 /**
1493 * Set the codemirror mode for all code cells, including the default for
1494 * new code cells.
1495 *
1496 * @method set_codemirror_mode
1497 */
1498 Notebook.prototype.set_codemirror_mode = function(newmode){
1499 if (newmode === this.codemirror_mode) {
1500 return;
1501 }
1502 this.codemirror_mode = newmode;
1503 codecell.CodeCell.options_default.cm_config.mode = newmode;
1504 modename = newmode.name || newmode
1505
1506 that = this;
1507 CodeMirror.requireMode(modename, function(){
1508 $.map(that.get_cells(), function(cell, i) {
1509 if (cell.cell_type === 'code'){
1510 cell.code_mirror.setOption('mode', newmode);
1511 // This is currently redundant, because cm_config ends up as
1512 // codemirror's own .options object, but I don't want to
1513 // rely on that.
1514 cell.cm_config.mode = newmode;
1515 }
1516 });
1517 })
1518 };
1406
1519
1407 // Session related things
1520 // Session related things
1408
1521
@@ -1411,8 +1524,21 b' var IPython = (function (IPython) {'
1411 *
1524 *
1412 * @method start_session
1525 * @method start_session
1413 */
1526 */
1414 Notebook.prototype.start_session = function () {
1527 Notebook.prototype.start_session = function (kernel_name) {
1415 this.session = new IPython.Session(this, this.options);
1528 if (kernel_name === undefined) {
1529 kernel_name = this.default_kernel_name;
1530 }
1531 this.session = new session.Session({
1532 base_url: this.base_url,
1533 ws_url: this.ws_url,
1534 notebook_path: this.notebook_path,
1535 notebook_name: this.notebook_name,
1536 // For now, create all sessions with the 'python' kernel, which is the
1537 // default. Later, the user will be able to select kernels. This is
1538 // overridden if KernelManager.kernel_cmd is specified for the server.
1539 kernel_name: kernel_name,
1540 notebook: this});
1541
1416 this.session.start($.proxy(this._session_started, this));
1542 this.session.start($.proxy(this._session_started, this));
1417 };
1543 };
1418
1544
@@ -1427,7 +1553,7 b' var IPython = (function (IPython) {'
1427 var ncells = this.ncells();
1553 var ncells = this.ncells();
1428 for (var i=0; i<ncells; i++) {
1554 for (var i=0; i<ncells; i++) {
1429 var cell = this.get_cell(i);
1555 var cell = this.get_cell(i);
1430 if (cell instanceof IPython.CodeCell) {
1556 if (cell instanceof codecell.CodeCell) {
1431 cell.set_kernel(this.session.kernel);
1557 cell.set_kernel(this.session.kernel);
1432 }
1558 }
1433 }
1559 }
@@ -1440,7 +1566,9 b' var IPython = (function (IPython) {'
1440 */
1566 */
1441 Notebook.prototype.restart_kernel = function () {
1567 Notebook.prototype.restart_kernel = function () {
1442 var that = this;
1568 var that = this;
1443 IPython.dialog.modal({
1569 dialog.modal({
1570 notebook: this,
1571 keyboard_manager: this.keyboard_manager,
1444 title : "Restart kernel or continue running?",
1572 title : "Restart kernel or continue running?",
1445 body : $("<p/>").text(
1573 body : $("<p/>").text(
1446 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1574 'Do you want to restart the current kernel? You will lose all variables defined in it.'
@@ -1633,6 +1761,13 b' var IPython = (function (IPython) {'
1633 this.metadata = content.metadata;
1761 this.metadata = content.metadata;
1634 this.notebook_name = data.name;
1762 this.notebook_name = data.name;
1635 var trusted = true;
1763 var trusted = true;
1764
1765 // Trigger an event changing the kernel spec - this will set the default
1766 // codemirror mode
1767 if (this.metadata.kernelspec !== undefined) {
1768 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1769 }
1770
1636 // Only handle 1 worksheet for now.
1771 // Only handle 1 worksheet for now.
1637 var worksheet = content.worksheets[0];
1772 var worksheet = content.worksheets[0];
1638 if (worksheet !== undefined) {
1773 if (worksheet !== undefined) {
@@ -1660,10 +1795,12 b' var IPython = (function (IPython) {'
1660 }
1795 }
1661 if (trusted != this.trusted) {
1796 if (trusted != this.trusted) {
1662 this.trusted = trusted;
1797 this.trusted = trusted;
1663 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1798 this.events.trigger("trust_changed.Notebook", trusted);
1664 }
1799 }
1665 if (content.worksheets.length > 1) {
1800 if (content.worksheets.length > 1) {
1666 IPython.dialog.modal({
1801 dialog.modal({
1802 notebook: this,
1803 keyboard_manager: this.keyboard_manager,
1667 title : "Multiple worksheets",
1804 title : "Multiple worksheets",
1668 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1805 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1669 "but this version of IPython can only handle the first. " +
1806 "but this version of IPython can only handle the first. " +
@@ -1705,7 +1842,7 b' var IPython = (function (IPython) {'
1705 };
1842 };
1706 if (trusted != this.trusted) {
1843 if (trusted != this.trusted) {
1707 this.trusted = trusted;
1844 this.trusted = trusted;
1708 $([IPython.events]).trigger("trust_changed.Notebook", trusted);
1845 this.events.trigger("trust_changed.Notebook", trusted);
1709 }
1846 }
1710 return data;
1847 return data;
1711 };
1848 };
@@ -1730,10 +1867,10 b' var IPython = (function (IPython) {'
1730 that.save_notebook();
1867 that.save_notebook();
1731 }
1868 }
1732 }, interval);
1869 }, interval);
1733 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1870 this.events.trigger("autosave_enabled.Notebook", interval);
1734 } else {
1871 } else {
1735 this.autosave_timer = null;
1872 this.autosave_timer = null;
1736 $([IPython.events]).trigger("autosave_disabled.Notebook");
1873 this.events.trigger("autosave_disabled.Notebook");
1737 }
1874 }
1738 };
1875 };
1739
1876
@@ -1748,6 +1885,8 b' var IPython = (function (IPython) {'
1748 var model = {};
1885 var model = {};
1749 model.name = this.notebook_name;
1886 model.name = this.notebook_name;
1750 model.path = this.notebook_path;
1887 model.path = this.notebook_path;
1888 model.type = 'notebook';
1889 model.format = 'json';
1751 model.content = this.toJSON();
1890 model.content = this.toJSON();
1752 model.content.nbformat = this.nbformat;
1891 model.content.nbformat = this.nbformat;
1753 model.content.nbformat_minor = this.nbformat_minor;
1892 model.content.nbformat_minor = this.nbformat_minor;
@@ -1768,10 +1907,10 b' var IPython = (function (IPython) {'
1768 settings[key] = extra_settings[key];
1907 settings[key] = extra_settings[key];
1769 }
1908 }
1770 }
1909 }
1771 $([IPython.events]).trigger('notebook_saving.Notebook');
1910 this.events.trigger('notebook_saving.Notebook');
1772 var url = utils.url_join_encode(
1911 var url = utils.url_join_encode(
1773 this.base_url,
1912 this.base_url,
1774 'api/notebooks',
1913 'api/contents',
1775 this.notebook_path,
1914 this.notebook_path,
1776 this.notebook_name
1915 this.notebook_name
1777 );
1916 );
@@ -1789,7 +1928,7 b' var IPython = (function (IPython) {'
1789 */
1928 */
1790 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1929 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1791 this.set_dirty(false);
1930 this.set_dirty(false);
1792 $([IPython.events]).trigger('notebook_saved.Notebook');
1931 this.events.trigger('notebook_saved.Notebook');
1793 this._update_autosave_interval(start);
1932 this._update_autosave_interval(start);
1794 if (this._checkpoint_after_save) {
1933 if (this._checkpoint_after_save) {
1795 this.create_checkpoint();
1934 this.create_checkpoint();
@@ -1826,7 +1965,7 b' var IPython = (function (IPython) {'
1826 * @param {String} error HTTP error message
1965 * @param {String} error HTTP error message
1827 */
1966 */
1828 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1967 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1829 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1968 this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1830 };
1969 };
1831
1970
1832 /**
1971 /**
@@ -1851,7 +1990,9 b' var IPython = (function (IPython) {'
1851 );
1990 );
1852
1991
1853 var nb = this;
1992 var nb = this;
1854 IPython.dialog.modal({
1993 dialog.modal({
1994 notebook: this,
1995 keyboard_manager: this.keyboard_manager,
1855 title: "Trust this notebook?",
1996 title: "Trust this notebook?",
1856 body: body,
1997 body: body,
1857
1998
@@ -1867,7 +2008,7 b' var IPython = (function (IPython) {'
1867 cell.output_area.trusted = true;
2008 cell.output_area.trusted = true;
1868 }
2009 }
1869 }
2010 }
1870 $([IPython.events]).on('notebook_saved.Notebook', function () {
2011 this.events.on('notebook_saved.Notebook', function () {
1871 window.location.reload();
2012 window.location.reload();
1872 });
2013 });
1873 nb.save_notebook();
2014 nb.save_notebook();
@@ -1902,7 +2043,7 b' var IPython = (function (IPython) {'
1902 };
2043 };
1903 var url = utils.url_join_encode(
2044 var url = utils.url_join_encode(
1904 base_url,
2045 base_url,
1905 'api/notebooks',
2046 'api/contents',
1906 path
2047 path
1907 );
2048 );
1908 $.ajax(url,settings);
2049 $.ajax(url,settings);
@@ -1931,7 +2072,7 b' var IPython = (function (IPython) {'
1931 };
2072 };
1932 var url = utils.url_join_encode(
2073 var url = utils.url_join_encode(
1933 base_url,
2074 base_url,
1934 'api/notebooks',
2075 'api/contents',
1935 path
2076 path
1936 );
2077 );
1937 $.ajax(url,settings);
2078 $.ajax(url,settings);
@@ -1953,10 +2094,10 b' var IPython = (function (IPython) {'
1953 success : $.proxy(that.rename_success, this),
2094 success : $.proxy(that.rename_success, this),
1954 error : $.proxy(that.rename_error, this)
2095 error : $.proxy(that.rename_error, this)
1955 };
2096 };
1956 $([IPython.events]).trigger('rename_notebook.Notebook', data);
2097 this.events.trigger('rename_notebook.Notebook', data);
1957 var url = utils.url_join_encode(
2098 var url = utils.url_join_encode(
1958 this.base_url,
2099 this.base_url,
1959 'api/notebooks',
2100 'api/contents',
1960 this.notebook_path,
2101 this.notebook_path,
1961 this.notebook_name
2102 this.notebook_name
1962 );
2103 );
@@ -1974,7 +2115,7 b' var IPython = (function (IPython) {'
1974 };
2115 };
1975 var url = utils.url_join_encode(
2116 var url = utils.url_join_encode(
1976 this.base_url,
2117 this.base_url,
1977 'api/notebooks',
2118 'api/contents',
1978 this.notebook_path,
2119 this.notebook_path,
1979 this.notebook_name
2120 this.notebook_name
1980 );
2121 );
@@ -1986,32 +2127,33 b' var IPython = (function (IPython) {'
1986 var name = this.notebook_name = json.name;
2127 var name = this.notebook_name = json.name;
1987 var path = json.path;
2128 var path = json.path;
1988 this.session.rename_notebook(name, path);
2129 this.session.rename_notebook(name, path);
1989 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
2130 this.events.trigger('notebook_renamed.Notebook', json);
1990 };
2131 };
1991
2132
1992 Notebook.prototype.rename_error = function (xhr, status, error) {
2133 Notebook.prototype.rename_error = function (xhr, status, error) {
1993 var that = this;
2134 var that = this;
1994 var dialog = $('<div/>').append(
2135 var dialog_body = $('<div/>').append(
1995 $("<p/>").addClass("rename-message")
2136 $("<p/>").text('This notebook name already exists.')
1996 .text('This notebook name already exists.')
1997 );
2137 );
1998 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
2138 this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1999 IPython.dialog.modal({
2139 dialog.modal({
2140 notebook: this,
2141 keyboard_manager: this.keyboard_manager,
2000 title: "Notebook Rename Error!",
2142 title: "Notebook Rename Error!",
2001 body: dialog,
2143 body: dialog_body,
2002 buttons : {
2144 buttons : {
2003 "Cancel": {},
2145 "Cancel": {},
2004 "OK": {
2146 "OK": {
2005 class: "btn-primary",
2147 class: "btn-primary",
2006 click: function () {
2148 click: function () {
2007 IPython.save_widget.rename_notebook();
2149 this.save_widget.rename_notebook({notebook:that});
2008 }}
2150 }}
2009 },
2151 },
2010 open : function (event, ui) {
2152 open : function (event, ui) {
2011 var that = $(this);
2153 var that = $(this);
2012 // Upon ENTER, click the OK button.
2154 // Upon ENTER, click the OK button.
2013 that.find('input[type="text"]').keydown(function (event, ui) {
2155 that.find('input[type="text"]').keydown(function (event, ui) {
2014 if (event.which === IPython.keyboard.keycodes.enter) {
2156 if (event.which === this.keyboard.keycodes.enter) {
2015 that.find('.btn-primary').first().click();
2157 that.find('.btn-primary').first().click();
2016 }
2158 }
2017 });
2159 });
@@ -2039,10 +2181,10 b' var IPython = (function (IPython) {'
2039 success : $.proxy(this.load_notebook_success,this),
2181 success : $.proxy(this.load_notebook_success,this),
2040 error : $.proxy(this.load_notebook_error,this),
2182 error : $.proxy(this.load_notebook_error,this),
2041 };
2183 };
2042 $([IPython.events]).trigger('notebook_loading.Notebook');
2184 this.events.trigger('notebook_loading.Notebook');
2043 var url = utils.url_join_encode(
2185 var url = utils.url_join_encode(
2044 this.base_url,
2186 this.base_url,
2045 'api/notebooks',
2187 'api/contents',
2046 this.notebook_path,
2188 this.notebook_path,
2047 this.notebook_name
2189 this.notebook_name
2048 );
2190 );
@@ -2077,7 +2219,9 b' var IPython = (function (IPython) {'
2077 "newer notebook format will be used and older versions of IPython " +
2219 "newer notebook format will be used and older versions of IPython " +
2078 "may not be able to read it. To keep the older version, close the " +
2220 "may not be able to read it. To keep the older version, close the " +
2079 "notebook without saving it.";
2221 "notebook without saving it.";
2080 IPython.dialog.modal({
2222 dialog.modal({
2223 notebook: this,
2224 keyboard_manager: this.keyboard_manager,
2081 title : "Notebook converted",
2225 title : "Notebook converted",
2082 body : msg,
2226 body : msg,
2083 buttons : {
2227 buttons : {
@@ -2094,7 +2238,9 b' var IPython = (function (IPython) {'
2094 this_vs + ". You can still work with this notebook, but some features " +
2238 this_vs + ". You can still work with this notebook, but some features " +
2095 "introduced in later notebook versions may not be available.";
2239 "introduced in later notebook versions may not be available.";
2096
2240
2097 IPython.dialog.modal({
2241 dialog.modal({
2242 notebook: this,
2243 keyboard_manager: this.keyboard_manager,
2098 title : "Newer Notebook",
2244 title : "Newer Notebook",
2099 body : msg,
2245 body : msg,
2100 buttons : {
2246 buttons : {
@@ -2109,22 +2255,25 b' var IPython = (function (IPython) {'
2109 // Create the session after the notebook is completely loaded to prevent
2255 // Create the session after the notebook is completely loaded to prevent
2110 // code execution upon loading, which is a security risk.
2256 // code execution upon loading, which is a security risk.
2111 if (this.session === null) {
2257 if (this.session === null) {
2112 this.start_session();
2258 var kernelspec = this.metadata.kernelspec || {};
2259 var kernel_name = kernelspec.name || this.default_kernel_name;
2260
2261 this.start_session(kernel_name);
2113 }
2262 }
2114 // load our checkpoint list
2263 // load our checkpoint list
2115 this.list_checkpoints();
2264 this.list_checkpoints();
2116
2265
2117 // load toolbar state
2266 // load toolbar state
2118 if (this.metadata.celltoolbar) {
2267 if (this.metadata.celltoolbar) {
2119 IPython.CellToolbar.global_show();
2268 celltoolbar.CellToolbar.global_show();
2120 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
2269 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2121 } else {
2270 } else {
2122 IPython.CellToolbar.global_hide();
2271 celltoolbar.CellToolbar.global_hide();
2123 }
2272 }
2124
2273
2125 // now that we're fully loaded, it is safe to restore save functionality
2274 // now that we're fully loaded, it is safe to restore save functionality
2126 delete(this.save_notebook);
2275 delete(this.save_notebook);
2127 $([IPython.events]).trigger('notebook_loaded.Notebook');
2276 this.events.trigger('notebook_loaded.Notebook');
2128 };
2277 };
2129
2278
2130 /**
2279 /**
@@ -2136,7 +2285,7 b' var IPython = (function (IPython) {'
2136 * @param {String} error HTTP error message
2285 * @param {String} error HTTP error message
2137 */
2286 */
2138 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2287 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2139 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2288 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2140 var msg;
2289 var msg;
2141 if (xhr.status === 400) {
2290 if (xhr.status === 400) {
2142 msg = error;
2291 msg = error;
@@ -2145,7 +2294,9 b' var IPython = (function (IPython) {'
2145 "This version can load notebook formats " +
2294 "This version can load notebook formats " +
2146 "v" + this.nbformat + " or earlier.";
2295 "v" + this.nbformat + " or earlier.";
2147 }
2296 }
2148 IPython.dialog.modal({
2297 dialog.modal({
2298 notebook: this,
2299 keyboard_manager: this.keyboard_manager,
2149 title: "Error loading notebook",
2300 title: "Error loading notebook",
2150 body : msg,
2301 body : msg,
2151 buttons : {
2302 buttons : {
@@ -2196,7 +2347,7 b' var IPython = (function (IPython) {'
2196 Notebook.prototype.list_checkpoints = function () {
2347 Notebook.prototype.list_checkpoints = function () {
2197 var url = utils.url_join_encode(
2348 var url = utils.url_join_encode(
2198 this.base_url,
2349 this.base_url,
2199 'api/notebooks',
2350 'api/contents',
2200 this.notebook_path,
2351 this.notebook_path,
2201 this.notebook_name,
2352 this.notebook_name,
2202 'checkpoints'
2353 'checkpoints'
@@ -2224,7 +2375,7 b' var IPython = (function (IPython) {'
2224 } else {
2375 } else {
2225 this.last_checkpoint = null;
2376 this.last_checkpoint = null;
2226 }
2377 }
2227 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2378 this.events.trigger('checkpoints_listed.Notebook', [data]);
2228 };
2379 };
2229
2380
2230 /**
2381 /**
@@ -2236,7 +2387,7 b' var IPython = (function (IPython) {'
2236 * @param {String} error_msg HTTP error message
2387 * @param {String} error_msg HTTP error message
2237 */
2388 */
2238 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2389 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2239 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2390 this.events.trigger('list_checkpoints_failed.Notebook');
2240 };
2391 };
2241
2392
2242 /**
2393 /**
@@ -2247,7 +2398,7 b' var IPython = (function (IPython) {'
2247 Notebook.prototype.create_checkpoint = function () {
2398 Notebook.prototype.create_checkpoint = function () {
2248 var url = utils.url_join_encode(
2399 var url = utils.url_join_encode(
2249 this.base_url,
2400 this.base_url,
2250 'api/notebooks',
2401 'api/contents',
2251 this.notebook_path,
2402 this.notebook_path,
2252 this.notebook_name,
2403 this.notebook_name,
2253 'checkpoints'
2404 'checkpoints'
@@ -2270,7 +2421,7 b' var IPython = (function (IPython) {'
2270 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2421 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2271 data = $.parseJSON(data);
2422 data = $.parseJSON(data);
2272 this.add_checkpoint(data);
2423 this.add_checkpoint(data);
2273 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2424 this.events.trigger('checkpoint_created.Notebook', data);
2274 };
2425 };
2275
2426
2276 /**
2427 /**
@@ -2282,7 +2433,7 b' var IPython = (function (IPython) {'
2282 * @param {String} error_msg HTTP error message
2433 * @param {String} error_msg HTTP error message
2283 */
2434 */
2284 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2435 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2285 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2436 this.events.trigger('checkpoint_failed.Notebook');
2286 };
2437 };
2287
2438
2288 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2439 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
@@ -2309,7 +2460,9 b' var IPython = (function (IPython) {'
2309 ).css("text-align", "center")
2460 ).css("text-align", "center")
2310 );
2461 );
2311
2462
2312 IPython.dialog.modal({
2463 dialog.modal({
2464 notebook: this,
2465 keyboard_manager: this.keyboard_manager,
2313 title : "Revert notebook to checkpoint",
2466 title : "Revert notebook to checkpoint",
2314 body : body,
2467 body : body,
2315 buttons : {
2468 buttons : {
@@ -2331,10 +2484,10 b' var IPython = (function (IPython) {'
2331 * @param {String} checkpoint ID
2484 * @param {String} checkpoint ID
2332 */
2485 */
2333 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2486 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2334 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2487 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2335 var url = utils.url_join_encode(
2488 var url = utils.url_join_encode(
2336 this.base_url,
2489 this.base_url,
2337 'api/notebooks',
2490 'api/contents',
2338 this.notebook_path,
2491 this.notebook_path,
2339 this.notebook_name,
2492 this.notebook_name,
2340 'checkpoints',
2493 'checkpoints',
@@ -2356,7 +2509,7 b' var IPython = (function (IPython) {'
2356 * @param {jqXHR} xhr jQuery Ajax object
2509 * @param {jqXHR} xhr jQuery Ajax object
2357 */
2510 */
2358 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2511 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2359 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2512 this.events.trigger('checkpoint_restored.Notebook');
2360 this.load_notebook(this.notebook_name, this.notebook_path);
2513 this.load_notebook(this.notebook_name, this.notebook_path);
2361 };
2514 };
2362
2515
@@ -2369,7 +2522,7 b' var IPython = (function (IPython) {'
2369 * @param {String} error_msg HTTP error message
2522 * @param {String} error_msg HTTP error message
2370 */
2523 */
2371 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2524 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2372 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2525 this.events.trigger('checkpoint_restore_failed.Notebook');
2373 };
2526 };
2374
2527
2375 /**
2528 /**
@@ -2379,10 +2532,10 b' var IPython = (function (IPython) {'
2379 * @param {String} checkpoint ID
2532 * @param {String} checkpoint ID
2380 */
2533 */
2381 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2534 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2382 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2535 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2383 var url = utils.url_join_encode(
2536 var url = utils.url_join_encode(
2384 this.base_url,
2537 this.base_url,
2385 'api/notebooks',
2538 'api/contents',
2386 this.notebook_path,
2539 this.notebook_path,
2387 this.notebook_name,
2540 this.notebook_name,
2388 'checkpoints',
2541 'checkpoints',
@@ -2404,7 +2557,7 b' var IPython = (function (IPython) {'
2404 * @param {jqXHR} xhr jQuery Ajax object
2557 * @param {jqXHR} xhr jQuery Ajax object
2405 */
2558 */
2406 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2559 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2407 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2560 this.events.trigger('checkpoint_deleted.Notebook', data);
2408 this.load_notebook(this.notebook_name, this.notebook_path);
2561 this.load_notebook(this.notebook_name, this.notebook_path);
2409 };
2562 };
2410
2563
@@ -2417,14 +2570,12 b' var IPython = (function (IPython) {'
2417 * @param {String} error_msg HTTP error message
2570 * @param {String} error_msg HTTP error message
2418 */
2571 */
2419 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2572 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2420 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2573 this.events.trigger('checkpoint_delete_failed.Notebook');
2421 };
2574 };
2422
2575
2423
2576
2577 // For backwards compatability.
2424 IPython.Notebook = Notebook;
2578 IPython.Notebook = Notebook;
2425
2579
2426
2580 return {'Notebook': Notebook};
2427 return IPython;
2581 });
2428
2429 }(IPython));
2430
@@ -1,21 +1,30 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2012 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
3
4 // Distributed under the terms of the BSD License. The full license is in
4 define([
5 // the file COPYING, distributed as part of this software.
5 'base/js/namespace',
6 //----------------------------------------------------------------------------
6 'jquery',
7
7 'base/js/utils',
8 //============================================================================
8 'base/js/dialog',
9 // Notification widget
9 'notebook/js/notificationwidget',
10 //============================================================================
10 ], function(IPython, $, utils, dialog, notificationwidget) {
11
12 var IPython = (function (IPython) {
13 "use strict";
11 "use strict";
14 var utils = IPython.utils;
15
12
16
13 var NotificationArea = function (selector, options) {
17 var NotificationArea = function (selector) {
14 // Constructor
15 //
16 // Parameters:
17 // selector: string
18 // options: dictionary
19 // Dictionary of keyword arguments.
20 // notebook: Notebook instance
21 // events: $(Events) instance
22 // save_widget: SaveWidget instance
18 this.selector = selector;
23 this.selector = selector;
24 this.events = options.events;
25 this.save_widget = options.save_widget;
26 this.notebook = options.notebook;
27 this.keyboard_manager = options.keyboard_manager;
19 if (this.selector !== undefined) {
28 if (this.selector !== undefined) {
20 this.element = $(selector);
29 this.element = $(selector);
21 }
30 }
@@ -23,13 +32,10 b' var IPython = (function (IPython) {'
23 };
32 };
24
33
25 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
34 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
26 var uuid = utils.uuid();
27 if( css_class == 'danger') {css_class = 'ui-state-error';}
35 if( css_class == 'danger') {css_class = 'ui-state-error';}
28 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
36 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
29 var tdiv = $('<div>')
37 var tdiv = $('<div>')
30 .attr('id',uuid)
38 .addClass('notification_widget')
31 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
32 .addClass('border-box-sizing')
33 .addClass(css_class)
39 .addClass(css_class)
34 .hide()
40 .hide()
35 .text(msg);
41 .text(msg);
@@ -63,46 +69,47 b' var IPython = (function (IPython) {'
63 }
69 }
64 var div = $('<div/>').attr('id','notification_'+name);
70 var div = $('<div/>').attr('id','notification_'+name);
65 $(this.selector).append(div);
71 $(this.selector).append(div);
66 this.widget_dict[name] = new IPython.NotificationWidget('#notification_'+name);
72 this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name);
67 return this.widget_dict[name];
73 return this.widget_dict[name];
68 };
74 };
69
75
70 NotificationArea.prototype.init_notification_widgets = function() {
76 NotificationArea.prototype.init_notification_widgets = function() {
77 var that = this;
71 var knw = this.new_notification_widget('kernel');
78 var knw = this.new_notification_widget('kernel');
72 var $kernel_ind_icon = $("#kernel_indicator_icon");
79 var $kernel_ind_icon = $("#kernel_indicator_icon");
73 var $modal_ind_icon = $("#modal_indicator_icon");
80 var $modal_ind_icon = $("#modal_indicator_icon");
74
81
75 // Command/Edit mode
82 // Command/Edit mode
76 $([IPython.events]).on('edit_mode.Notebook',function () {
83 this.events.on('edit_mode.Notebook',function () {
77 IPython.save_widget.update_document_title();
84 that.save_widget.update_document_title();
78 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
85 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
79 });
86 });
80
87
81 $([IPython.events]).on('command_mode.Notebook',function () {
88 this.events.on('command_mode.Notebook',function () {
82 IPython.save_widget.update_document_title();
89 that.save_widget.update_document_title();
83 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
90 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
84 });
91 });
85
92
86 // Implicitly start off in Command mode, switching to Edit mode will trigger event
93 // 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');
94 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
88
95
89 // Kernel events
96 // Kernel events
90 $([IPython.events]).on('status_idle.Kernel',function () {
97 this.events.on('status_idle.Kernel',function () {
91 IPython.save_widget.update_document_title();
98 that.save_widget.update_document_title();
92 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
99 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
93 });
100 });
94
101
95 $([IPython.events]).on('status_busy.Kernel',function () {
102 this.events.on('status_busy.Kernel',function () {
96 window.document.title='(Busy) '+window.document.title;
103 window.document.title='(Busy) '+window.document.title;
97 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
104 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
98 });
105 });
99
106
100 $([IPython.events]).on('status_restarting.Kernel',function () {
107 this.events.on('status_restarting.Kernel',function () {
101 IPython.save_widget.update_document_title();
108 that.save_widget.update_document_title();
102 knw.set_message("Restarting kernel", 2000);
109 knw.set_message("Restarting kernel", 2000);
103 });
110 });
104
111
105 $([IPython.events]).on('status_interrupting.Kernel',function () {
112 this.events.on('status_interrupting.Kernel',function () {
106 knw.set_message("Interrupting kernel", 2000);
113 knw.set_message("Interrupting kernel", 2000);
107 });
114 });
108
115
@@ -110,28 +117,30 b' var IPython = (function (IPython) {'
110 // When the kernel_info reply arrives, the kernel is idle.
117 // When the kernel_info reply arrives, the kernel is idle.
111 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
118 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
112
119
113 $([IPython.events]).on('status_started.Kernel', function (evt, data) {
120 this.events.on('status_started.Kernel', function (evt, data) {
114 data.kernel.kernel_info(function () {
121 data.kernel.kernel_info(function () {
115 $([IPython.events]).trigger('status_idle.Kernel');
122 that.events.trigger('status_idle.Kernel');
116 });
123 });
117 });
124 });
118
125
119 $([IPython.events]).on('status_dead.Kernel',function () {
126 this.events.on('status_dead.Kernel',function () {
120 var msg = 'The kernel has died, and the automatic restart has failed.' +
127 var msg = 'The kernel has died, and the automatic restart has failed.' +
121 ' It is possible the kernel cannot be restarted.' +
128 ' It is possible the kernel cannot be restarted.' +
122 ' If you are not able to restart the kernel, you will still be able to save' +
129 ' If you are not able to restart the kernel, you will still be able to save' +
123 ' the notebook, but running code will no longer work until the notebook' +
130 ' the notebook, but running code will no longer work until the notebook' +
124 ' is reopened.';
131 ' is reopened.';
125
132
126 IPython.dialog.modal({
133 dialog.modal({
127 title: "Dead kernel",
134 title: "Dead kernel",
128 body : msg,
135 body : msg,
136 keyboard_manager: that.keyboard_manager,
137 notebook: that.notebook,
129 buttons : {
138 buttons : {
130 "Manual Restart": {
139 "Manual Restart": {
131 class: "btn-danger",
140 class: "btn-danger",
132 click: function () {
141 click: function () {
133 $([IPython.events]).trigger('status_restarting.Kernel');
142 that.events.trigger('status_restarting.Kernel');
134 IPython.notebook.start_kernel();
143 that.notebook.start_kernel();
135 }
144 }
136 },
145 },
137 "Don't restart": {}
146 "Don't restart": {}
@@ -139,7 +148,7 b' var IPython = (function (IPython) {'
139 });
148 });
140 });
149 });
141
150
142 $([IPython.events]).on('websocket_closed.Kernel', function (event, data) {
151 this.events.on('websocket_closed.Kernel', function (event, data) {
143 var kernel = data.kernel;
152 var kernel = data.kernel;
144 var ws_url = data.ws_url;
153 var ws_url = data.ws_url;
145 var early = data.early;
154 var early = data.early;
@@ -155,9 +164,11 b' var IPython = (function (IPython) {'
155 msg = "A WebSocket connection could not be established." +
164 msg = "A WebSocket connection could not be established." +
156 " You will NOT be able to run code. Check your" +
165 " You will NOT be able to run code. Check your" +
157 " network connection or notebook server configuration.";
166 " network connection or notebook server configuration.";
158 IPython.dialog.modal({
167 dialog.modal({
159 title: "WebSocket connection failed",
168 title: "WebSocket connection failed",
160 body: msg,
169 body: msg,
170 keyboard_manager: that.keyboard_manager,
171 notebook: that.notebook,
161 buttons : {
172 buttons : {
162 "OK": {},
173 "OK": {},
163 "Reconnect": {
174 "Reconnect": {
@@ -176,24 +187,24 b' var IPython = (function (IPython) {'
176 var nnw = this.new_notification_widget('notebook');
187 var nnw = this.new_notification_widget('notebook');
177
188
178 // Notebook events
189 // Notebook events
179 $([IPython.events]).on('notebook_loading.Notebook', function () {
190 this.events.on('notebook_loading.Notebook', function () {
180 nnw.set_message("Loading notebook",500);
191 nnw.set_message("Loading notebook",500);
181 });
192 });
182 $([IPython.events]).on('notebook_loaded.Notebook', function () {
193 this.events.on('notebook_loaded.Notebook', function () {
183 nnw.set_message("Notebook loaded",500);
194 nnw.set_message("Notebook loaded",500);
184 });
195 });
185 $([IPython.events]).on('notebook_saving.Notebook', function () {
196 this.events.on('notebook_saving.Notebook', function () {
186 nnw.set_message("Saving notebook",500);
197 nnw.set_message("Saving notebook",500);
187 });
198 });
188 $([IPython.events]).on('notebook_saved.Notebook', function () {
199 this.events.on('notebook_saved.Notebook', function () {
189 nnw.set_message("Notebook saved",2000);
200 nnw.set_message("Notebook saved",2000);
190 });
201 });
191 $([IPython.events]).on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
202 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
192 nnw.set_message(data || "Notebook save failed");
203 nnw.warning(data || "Notebook save failed");
193 });
204 });
194
205
195 // Checkpoint events
206 // Checkpoint events
196 $([IPython.events]).on('checkpoint_created.Notebook', function (evt, data) {
207 this.events.on('checkpoint_created.Notebook', function (evt, data) {
197 var msg = "Checkpoint created";
208 var msg = "Checkpoint created";
198 if (data.last_modified) {
209 if (data.last_modified) {
199 var d = new Date(data.last_modified);
210 var d = new Date(data.last_modified);
@@ -201,27 +212,27 b' var IPython = (function (IPython) {'
201 }
212 }
202 nnw.set_message(msg, 2000);
213 nnw.set_message(msg, 2000);
203 });
214 });
204 $([IPython.events]).on('checkpoint_failed.Notebook', function () {
215 this.events.on('checkpoint_failed.Notebook', function () {
205 nnw.set_message("Checkpoint failed");
216 nnw.warning("Checkpoint failed");
206 });
217 });
207 $([IPython.events]).on('checkpoint_deleted.Notebook', function () {
218 this.events.on('checkpoint_deleted.Notebook', function () {
208 nnw.set_message("Checkpoint deleted", 500);
219 nnw.set_message("Checkpoint deleted", 500);
209 });
220 });
210 $([IPython.events]).on('checkpoint_delete_failed.Notebook', function () {
221 this.events.on('checkpoint_delete_failed.Notebook', function () {
211 nnw.set_message("Checkpoint delete failed");
222 nnw.warning("Checkpoint delete failed");
212 });
223 });
213 $([IPython.events]).on('checkpoint_restoring.Notebook', function () {
224 this.events.on('checkpoint_restoring.Notebook', function () {
214 nnw.set_message("Restoring to checkpoint...", 500);
225 nnw.set_message("Restoring to checkpoint...", 500);
215 });
226 });
216 $([IPython.events]).on('checkpoint_restore_failed.Notebook', function () {
227 this.events.on('checkpoint_restore_failed.Notebook', function () {
217 nnw.set_message("Checkpoint restore failed");
228 nnw.warning("Checkpoint restore failed");
218 });
229 });
219
230
220 // Autosave events
231 // Autosave events
221 $([IPython.events]).on('autosave_disabled.Notebook', function () {
232 this.events.on('autosave_disabled.Notebook', function () {
222 nnw.set_message("Autosave disabled", 2000);
233 nnw.set_message("Autosave disabled", 2000);
223 });
234 });
224 $([IPython.events]).on('autosave_enabled.Notebook', function (evt, interval) {
235 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
225 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
236 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
226 });
237 });
227
238
@@ -229,7 +240,5 b' var IPython = (function (IPython) {'
229
240
230 IPython.NotificationArea = NotificationArea;
241 IPython.NotificationArea = NotificationArea;
231
242
232 return IPython;
243 return {'NotificationArea': NotificationArea};
233
244 });
234 }(IPython));
235
@@ -1,18 +1,11 b''
1 //----------------------------------------------------------------------------
1 // Copyright (c) IPython Development Team.
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Distributed under the terms of the Modified BSD License.
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
3
8 //============================================================================
4 define([
9 // Notification widget
5 'base/js/namespace',
10 //============================================================================
6 'jquery',
11
7 ], function(IPython, $) {
12 var IPython = (function (IPython) {
13 "use strict";
8 "use strict";
14 var utils = IPython.utils;
15
16
9
17 var NotificationWidget = function (selector) {
10 var NotificationWidget = function (selector) {
18 this.selector = selector;
11 this.selector = selector;
@@ -22,7 +15,6 b' var IPython = (function (IPython) {'
22 this.element = $(selector);
15 this.element = $(selector);
23 this.style();
16 this.style();
24 }
17 }
25 this.element.button();
26 this.element.hide();
18 this.element.hide();
27 var that = this;
19 var that = this;
28
20
@@ -31,10 +23,8 b' var IPython = (function (IPython) {'
31
23
32 };
24 };
33
25
34
35 NotificationWidget.prototype.style = function () {
26 NotificationWidget.prototype.style = function () {
36 this.element.addClass('notification_widget pull-right');
27 this.element.addClass('notification_widget');
37 this.element.addClass('border-box-sizing');
38 };
28 };
39
29
40 // msg : message to display
30 // msg : message to display
@@ -43,14 +33,24 b' var IPython = (function (IPython) {'
43 // if timeout <= 0
33 // if timeout <= 0
44 // click_callback : function called if user click on notification
34 // click_callback : function called if user click on notification
45 // could return false to prevent the notification to be dismissed
35 // could return false to prevent the notification to be dismissed
46 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, opts) {
36 NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) {
47 var opts = opts || {};
37 var options = options || {};
48 var callback = click_callback || function() {return false;};
38 var callback = click_callback || function() {return true;};
49 var that = this;
39 var that = this;
50 this.inner.attr('class', opts.icon);
40 // unbind potential previous callback
51 this.inner.attr('title', opts.title);
41 this.element.unbind('click');
42 this.inner.attr('class', options.icon);
43 this.inner.attr('title', options.title);
52 this.inner.text(msg);
44 this.inner.text(msg);
53 this.element.fadeIn(100);
45 this.element.fadeIn(100);
46
47 // reset previous set style
48 this.element.removeClass();
49 this.style();
50 if (options.class){
51
52 this.element.addClass(options.class)
53 }
54 if (this.timeout !== null) {
54 if (this.timeout !== null) {
55 clearTimeout(this.timeout);
55 clearTimeout(this.timeout);
56 this.timeout = null;
56 this.timeout = null;
@@ -62,7 +62,7 b' var IPython = (function (IPython) {'
62 }, timeout);
62 }, timeout);
63 } else {
63 } else {
64 this.element.click(function() {
64 this.element.click(function() {
65 if( callback() != false ) {
65 if( callback() !== false ) {
66 that.element.fadeOut(100, function () {that.inner.text('');});
66 that.element.fadeOut(100, function () {that.inner.text('');});
67 that.element.unbind('click');
67 that.element.unbind('click');
68 }
68 }
@@ -75,14 +75,30 b' var IPython = (function (IPython) {'
75 };
75 };
76
76
77
77
78 NotificationWidget.prototype.info = function (msg, timeout, click_callback, options) {
79 var options = options || {};
80 options.class = options.class +' info';
81 var timeout = timeout || 3500;
82 this.set_message(msg, timeout, click_callback, options);
83 }
84 NotificationWidget.prototype.warning = function (msg, timeout, click_callback, options) {
85 var options = options || {};
86 options.class = options.class +' warning';
87 this.set_message(msg, timeout, click_callback, options);
88 }
89 NotificationWidget.prototype.danger = function (msg, timeout, click_callback, options) {
90 var options = options || {};
91 options.class = options.class +' danger';
92 this.set_message(msg, timeout, click_callback, options);
93 }
94
95
78 NotificationWidget.prototype.get_message = function () {
96 NotificationWidget.prototype.get_message = function () {
79 return this.inner.html();
97 return this.inner.html();
80 };
98 };
81
99
82
100 // For backwards compatibility.
83 IPython.NotificationWidget = NotificationWidget;
101 IPython.NotificationWidget = NotificationWidget;
84
102
85 return IPython;
103 return {'NotificationWidget': NotificationWidget};
86
104 });
87 }(IPython));
88
@@ -1,38 +1,37 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 //============================================================================
4 define([
5 // OutputArea
5 'base/js/namespace',
6 //============================================================================
6 'jqueryui',
7
7 'base/js/utils',
8 /**
8 'base/js/security',
9 * @module IPython
9 'base/js/keyboard',
10 * @namespace IPython
10 'notebook/js/mathjaxutils',
11 * @submodule OutputArea
11 'components/marked/lib/marked',
12 */
12 ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
13 var IPython = (function (IPython) {
14 "use strict";
13 "use strict";
15
14
16 var utils = IPython.utils;
17
18 /**
15 /**
19 * @class OutputArea
16 * @class OutputArea
20 *
17 *
21 * @constructor
18 * @constructor
22 */
19 */
23
20
24 var OutputArea = function (selector, prompt_area) {
21 var OutputArea = function (options) {
25 this.selector = selector;
22 this.selector = options.selector;
26 this.wrapper = $(selector);
23 this.events = options.events;
24 this.keyboard_manager = options.keyboard_manager;
25 this.wrapper = $(options.selector);
27 this.outputs = [];
26 this.outputs = [];
28 this.collapsed = false;
27 this.collapsed = false;
29 this.scrolled = false;
28 this.scrolled = false;
30 this.trusted = true;
29 this.trusted = true;
31 this.clear_queued = null;
30 this.clear_queued = null;
32 if (prompt_area === undefined) {
31 if (options.prompt_area === undefined) {
33 this.prompt_area = true;
32 this.prompt_area = true;
34 } else {
33 } else {
35 this.prompt_area = prompt_area;
34 this.prompt_area = options.prompt_area;
36 }
35 }
37 this.create_elements();
36 this.create_elements();
38 this.style();
37 this.style();
@@ -101,7 +100,7 b' var IPython = (function (IPython) {'
101
100
102 this.element.resize(function () {
101 this.element.resize(function () {
103 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
104 if ( IPython.utils.browser[0] === "Firefox" ) {
103 if ( utils.browser[0] === "Firefox" ) {
105 return;
104 return;
106 }
105 }
107 // maybe scroll output,
106 // maybe scroll output,
@@ -282,12 +281,15 b' var IPython = (function (IPython) {'
282 needs_height_reset = true;
281 needs_height_reset = true;
283 }
282 }
284
283
284 var record_output = true;
285
285 if (json.output_type === 'execute_result') {
286 if (json.output_type === 'execute_result') {
286 this.append_execute_result(json);
287 this.append_execute_result(json);
287 } else if (json.output_type === 'error') {
288 } else if (json.output_type === 'error') {
288 this.append_error(json);
289 this.append_error(json);
289 } else if (json.output_type === 'stream') {
290 } else if (json.output_type === 'stream') {
290 this.append_stream(json);
291 // append_stream might have merged the output with earlier stream output
292 record_output = this.append_stream(json);
291 }
293 }
292
294
293 // We must release the animation fixed height in a callback since Gecko
295 // We must release the animation fixed height in a callback since Gecko
@@ -308,7 +310,9 b' var IPython = (function (IPython) {'
308 handle_appended();
310 handle_appended();
309 }
311 }
310
312
311 this.outputs.push(json);
313 if (record_output) {
314 this.outputs.push(json);
315 }
312 };
316 };
313
317
314
318
@@ -459,20 +463,23 b' var IPython = (function (IPython) {'
459 // latest output was in the same stream,
463 // latest output was in the same stream,
460 // so append directly into its pre tag
464 // so append directly into its pre tag
461 // escape ANSI & HTML specials:
465 // escape ANSI & HTML specials:
466 last.text = utils.fixCarriageReturn(last.text + json.text);
462 var pre = this.element.find('div.'+subclass).last().find('pre');
467 var pre = this.element.find('div.'+subclass).last().find('pre');
463 var html = utils.fixCarriageReturn(
468 var html = utils.fixConsole(last.text);
464 pre.html() + utils.fixConsole(text));
465 // The only user content injected with this HTML call is
469 // The only user content injected with this HTML call is
466 // escaped by the fixConsole() method.
470 // escaped by the fixConsole() method.
467 pre.html(html);
471 pre.html(html);
468 return;
472 // return false signals that we merged this output with the previous one,
473 // and the new output shouldn't be recorded.
474 return false;
469 }
475 }
470 }
476 }
471
477
472 if (!text.replace("\r", "")) {
478 if (!text.replace("\r", "")) {
473 // text is nothing (empty string, \r, etc.)
479 // text is nothing (empty string, \r, etc.)
474 // so don't append any elements, which might add undesirable space
480 // so don't append any elements, which might add undesirable space
475 return;
481 // return true to indicate the output should be recorded.
482 return true;
476 }
483 }
477
484
478 // If we got here, attach a new div
485 // If we got here, attach a new div
@@ -482,6 +489,7 b' var IPython = (function (IPython) {'
482 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
489 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
483 }
490 }
484 this._safe_append(toinsert);
491 this._safe_append(toinsert);
492 return true;
485 };
493 };
486
494
487
495
@@ -515,7 +523,7 b' var IPython = (function (IPython) {'
515 if (!this.trusted && !OutputArea.safe_outputs[type]) {
523 if (!this.trusted && !OutputArea.safe_outputs[type]) {
516 // not trusted, sanitize HTML
524 // not trusted, sanitize HTML
517 if (type==='text/html' || type==='text/svg') {
525 if (type==='text/html' || type==='text/svg') {
518 value = IPython.security.sanitize_html(value);
526 value = security.sanitize_html(value);
519 } else {
527 } else {
520 // don't display if we don't know how to sanitize it
528 // don't display if we don't know how to sanitize it
521 console.log("Ignoring untrusted " + type + " output.");
529 console.log("Ignoring untrusted " + type + " output.");
@@ -531,7 +539,7 b' var IPython = (function (IPython) {'
531 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
539 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
532 setTimeout(handle_inserted, 0);
540 setTimeout(handle_inserted, 0);
533 }
541 }
534 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
542 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
535 return toinsert;
543 return toinsert;
536 }
544 }
537 }
545 }
@@ -542,7 +550,7 b' var IPython = (function (IPython) {'
542 var append_html = function (html, md, element) {
550 var append_html = function (html, md, element) {
543 var type = 'text/html';
551 var type = 'text/html';
544 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
552 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
545 IPython.keyboard_manager.register_events(toinsert);
553 this.keyboard_manager.register_events(toinsert);
546 toinsert.append(html);
554 toinsert.append(html);
547 element.append(toinsert);
555 element.append(toinsert);
548 return toinsert;
556 return toinsert;
@@ -552,11 +560,11 b' var IPython = (function (IPython) {'
552 var append_markdown = function(markdown, md, element) {
560 var append_markdown = function(markdown, md, element) {
553 var type = 'text/markdown';
561 var type = 'text/markdown';
554 var toinsert = this.create_output_subarea(md, "output_markdown", type);
562 var toinsert = this.create_output_subarea(md, "output_markdown", type);
555 var text_and_math = IPython.mathjaxutils.remove_math(markdown);
563 var text_and_math = mathjaxutils.remove_math(markdown);
556 var text = text_and_math[0];
564 var text = text_and_math[0];
557 var math = text_and_math[1];
565 var math = text_and_math[1];
558 var html = marked.parser(marked.lexer(text));
566 var html = marked.parser(marked.lexer(text));
559 html = IPython.mathjaxutils.replace_math(html, math);
567 html = mathjaxutils.replace_math(html, math);
560 toinsert.append(html);
568 toinsert.append(html);
561 element.append(toinsert);
569 element.append(toinsert);
562 return toinsert;
570 return toinsert;
@@ -567,13 +575,8 b' var IPython = (function (IPython) {'
567 // We just eval the JS code, element appears in the local scope.
575 // We just eval the JS code, element appears in the local scope.
568 var type = 'application/javascript';
576 var type = 'application/javascript';
569 var toinsert = this.create_output_subarea(md, "output_javascript", type);
577 var toinsert = this.create_output_subarea(md, "output_javascript", type);
570 IPython.keyboard_manager.register_events(toinsert);
578 this.keyboard_manager.register_events(toinsert);
571 element.append(toinsert);
579 element.append(toinsert);
572 // FIXME TODO : remove `container element for 3.0`
573 //backward compat, js should be eval'ed in a context where `container` is defined.
574 var container = element;
575 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
576 // end backward compat
577
580
578 // Fix for ipython/issues/5293, make sure `element` is the area which
581 // Fix for ipython/issues/5293, make sure `element` is the area which
579 // output can be inserted into at the time of JS execution.
582 // output can be inserted into at the time of JS execution.
@@ -763,7 +766,7 b' var IPython = (function (IPython) {'
763 .keydown(function (event, ui) {
766 .keydown(function (event, ui) {
764 // make sure we submit on enter,
767 // make sure we submit on enter,
765 // and don't re-execute the *cell* on shift-enter
768 // and don't re-execute the *cell* on shift-enter
766 if (event.which === IPython.keyboard.keycodes.enter) {
769 if (event.which === keyboard.keycodes.enter) {
767 that._submit_raw_input();
770 that._submit_raw_input();
768 return false;
771 return false;
769 }
772 }
@@ -775,7 +778,7 b' var IPython = (function (IPython) {'
775 var raw_input = area.find('input.raw_input');
778 var raw_input = area.find('input.raw_input');
776 // Register events that enable/disable the keyboard manager while raw
779 // Register events that enable/disable the keyboard manager while raw
777 // input is focused.
780 // input is focused.
778 IPython.keyboard_manager.register_events(raw_input);
781 this.keyboard_manager.register_events(raw_input);
779 // Note, the following line used to read raw_input.focus().focus().
782 // Note, the following line used to read raw_input.focus().focus().
780 // This seemed to be needed otherwise only the cell would be focused.
783 // This seemed to be needed otherwise only the cell would be focused.
781 // But with the modal UI, this seems to work fine with one call to focus().
784 // But with the modal UI, this seems to work fine with one call to focus().
@@ -794,14 +797,14 b' var IPython = (function (IPython) {'
794 }
797 }
795 var content = {
798 var content = {
796 output_type : 'stream',
799 output_type : 'stream',
797 name : 'stdout',
800 stream : 'stdout',
798 text : theprompt.text() + echo + '\n'
801 text : theprompt.text() + echo + '\n'
799 }
802 }
800 // remove form container
803 // remove form container
801 container.parent().remove();
804 container.parent().remove();
802 // replace with plaintext version in stdout
805 // replace with plaintext version in stdout
803 this.append_output(content, false);
806 this.append_output(content, false);
804 $([IPython.events]).trigger('send_input_reply.Kernel', value);
807 this.events.trigger('send_input_reply.Kernel', value);
805 }
808 }
806
809
807
810
@@ -992,8 +995,8 b' var IPython = (function (IPython) {'
992 "application/pdf" : append_pdf
995 "application/pdf" : append_pdf
993 };
996 };
994
997
998 // For backwards compatability.
995 IPython.OutputArea = OutputArea;
999 IPython.OutputArea = OutputArea;
996
1000
997 return IPython;
1001 return {'OutputArea': OutputArea};
998
1002 });
999 }(IPython));
@@ -1,20 +1,29 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 //============================================================================
4 define([
5 // Pager
5 'base/js/namespace',
6 //============================================================================
6 'jqueryui',
7
7 'base/js/utils',
8 var IPython = (function (IPython) {
8 ], function(IPython, $, utils) {
9 "use strict";
9 "use strict";
10
10
11 var utils = IPython.utils;
11 var Pager = function (pager_selector, pager_splitter_selector, options) {
12
12 // Constructor
13 var Pager = function (pager_selector, pager_splitter_selector) {
13 //
14 // Parameters:
15 // pager_selector: string
16 // pager_splitter_selector: string
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // events: $(Events) instance
20 // layout_manager: LayoutManager instance
21 this.events = options.events;
14 this.pager_element = $(pager_selector);
22 this.pager_element = $(pager_selector);
15 this.pager_button_area = $('#pager_button_area');
23 this.pager_button_area = $('#pager_button_area');
16 var that = this;
24 var that = this;
17 this.percentage_height = 0.40;
25 this.percentage_height = 0.40;
26 options.layout_manager.pager = this;
18 this.pager_splitter_element = $(pager_splitter_selector)
27 this.pager_splitter_element = $(pager_splitter_selector)
19 .draggable({
28 .draggable({
20 containment: 'window',
29 containment: 'window',
@@ -23,7 +32,7 b' var IPython = (function (IPython) {'
23 drag: function(event, ui) {
32 drag: function(event, ui) {
24 // recalculate the amount of space the pager should take
33 // recalculate the amount of space the pager should take
25 var pheight = ($(document.body).height()-event.clientY-4);
34 var pheight = ($(document.body).height()-event.clientY-4);
26 var downprct = pheight/IPython.layout_manager.app_height();
35 var downprct = pheight/options.layout_manager.app_height();
27 downprct = Math.min(0.9, downprct);
36 downprct = Math.min(0.9, downprct);
28 if (downprct < 0.1) {
37 if (downprct < 0.1) {
29 that.percentage_height = 0.1;
38 that.percentage_height = 0.1;
@@ -32,7 +41,7 b' var IPython = (function (IPython) {'
32 that.percentage_height = downprct;
41 that.percentage_height = downprct;
33 that.expand({'duration':0});
42 that.expand({'duration':0});
34 }
43 }
35 IPython.layout_manager.do_resize();
44 options.layout_manager.do_resize();
36 }
45 }
37 });
46 });
38 this.expanded = false;
47 this.expanded = false;
@@ -47,28 +56,26 b' var IPython = (function (IPython) {'
47 $('<a>').attr('role', "button")
56 $('<a>').attr('role', "button")
48 .attr('title',"Open the pager in an external window")
57 .attr('title',"Open the pager in an external window")
49 .addClass('ui-button')
58 .addClass('ui-button')
50 .click(function(){that.detach()})
59 .click(function(){that.detach();})
51 .attr('style','position: absolute; right: 20px;')
60 .attr('style','position: absolute; right: 20px;')
52 .append(
61 .append(
53 $('<span>').addClass("ui-icon ui-icon-extlink")
62 $('<span>').addClass("ui-icon ui-icon-extlink")
54 )
63 )
55 )
64 );
56 this.pager_button_area.append(
65 this.pager_button_area.append(
57 $('<a>').attr('role', "button")
66 $('<a>').attr('role', "button")
58 .attr('title',"Close the pager")
67 .attr('title',"Close the pager")
59 .addClass('ui-button')
68 .addClass('ui-button')
60 .click(function(){that.collapse()})
69 .click(function(){that.collapse();})
61 .attr('style','position: absolute; right: 5px;')
70 .attr('style','position: absolute; right: 5px;')
62 .append(
71 .append(
63 $('<span>').addClass("ui-icon ui-icon-close")
72 $('<span>').addClass("ui-icon ui-icon-close")
64 )
73 )
65 )
74 );
66 };
75 };
67
76
68 Pager.prototype.style = function () {
77 Pager.prototype.style = function () {
69 this.pager_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
78 this.pager_splitter_element.addClass('ui-widget ui-state-default');
70 this.pager_element.addClass('border-box-sizing');
71 this.pager_element.find(".container").addClass('border-box-sizing');
72 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
79 this.pager_splitter_element.attr('title', 'Click to Show/Hide pager area, drag to Resize');
73 };
80 };
74
81
@@ -105,7 +112,7 b' var IPython = (function (IPython) {'
105 that.toggle();
112 that.toggle();
106 });
113 });
107
114
108 $([IPython.events]).on('open_with_text.Pager', function (event, payload) {
115 this.events.on('open_with_text.Pager', function (event, payload) {
109 // FIXME: support other mime types
116 // FIXME: support other mime types
110 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
117 if (payload.data['text/plain'] && payload.data['text/plain'] !== "") {
111 that.clear();
118 that.clear();
@@ -171,10 +178,8 b' var IPython = (function (IPython) {'
171 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
178 this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text))));
172 };
179 };
173
180
174
181 // Backwards compatability.
175 IPython.Pager = Pager;
182 IPython.Pager = Pager;
176
183
177 return IPython;
184 return {'Pager': Pager};
178
185 });
179 }(IPython));
180
@@ -1,16 +1,28 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 //============================================================================
4 define([
5 // QuickHelp button
5 'base/js/namespace',
6 //============================================================================
6 'jquery',
7
7 'base/js/utils',
8 var IPython = (function (IPython) {
8 'base/js/dialog',
9 ], function(IPython, $, utils, dialog) {
9 "use strict";
10 "use strict";
10
11 var platform = utils.platform;
11 var platform = IPython.utils.platform;
12
12
13 var QuickHelp = function (options) {
13 var QuickHelp = function (selector) {
14 // Constructor
15 //
16 // Parameters:
17 // options: dictionary
18 // Dictionary of keyword arguments.
19 // events: $(Events) instance
20 // keyboard_manager: KeyboardManager instance
21 // notebook: Notebook instance
22 this.keyboard_manager = options.keyboard_manager;
23 this.notebook = options.notebook;
24 this.keyboard_manager.quick_help = this;
25 this.events = options.events;
14 };
26 };
15
27
16 var cmd_ctrl = 'Ctrl-';
28 var cmd_ctrl = 'Ctrl-';
@@ -70,8 +82,8 b' var IPython = (function (IPython) {'
70 $(this.shortcut_dialog).modal("toggle");
82 $(this.shortcut_dialog).modal("toggle");
71 return;
83 return;
72 }
84 }
73 var command_shortcuts = IPython.keyboard_manager.command_shortcuts.help();
85 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
74 var edit_shortcuts = IPython.keyboard_manager.edit_shortcuts.help();
86 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
75 var help, shortcut;
87 var help, shortcut;
76 var i, half, n;
88 var i, half, n;
77 var element = $('<div/>');
89 var element = $('<div/>');
@@ -96,21 +108,23 b' var IPython = (function (IPython) {'
96 var edit_div = this.build_edit_help(cm_shortcuts);
108 var edit_div = this.build_edit_help(cm_shortcuts);
97 element.append(edit_div);
109 element.append(edit_div);
98
110
99 this.shortcut_dialog = IPython.dialog.modal({
111 this.shortcut_dialog = dialog.modal({
100 title : "Keyboard shortcuts",
112 title : "Keyboard shortcuts",
101 body : element,
113 body : element,
102 destroy : false,
114 destroy : false,
103 buttons : {
115 buttons : {
104 Close : {}
116 Close : {}
105 }
117 },
118 notebook: this.notebook,
119 keyboard_manager: this.keyboard_manager,
106 });
120 });
107 this.shortcut_dialog.addClass("modal_stretch");
121 this.shortcut_dialog.addClass("modal_stretch");
108
122
109 $([IPython.events]).on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
123 this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;});
110 };
124 };
111
125
112 QuickHelp.prototype.build_command_help = function () {
126 QuickHelp.prototype.build_command_help = function () {
113 var command_shortcuts = IPython.keyboard_manager.command_shortcuts.help();
127 var command_shortcuts = this.keyboard_manager.command_shortcuts.help();
114 return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
128 return build_div('<h4>Command Mode (press <code>Esc</code> to enable)</h4>', command_shortcuts);
115 };
129 };
116
130
@@ -134,7 +148,7 b' var IPython = (function (IPython) {'
134 };
148 };
135
149
136 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
150 QuickHelp.prototype.build_edit_help = function (cm_shortcuts) {
137 var edit_shortcuts = IPython.keyboard_manager.edit_shortcuts.help();
151 var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help();
138 jQuery.merge(cm_shortcuts, edit_shortcuts);
152 jQuery.merge(cm_shortcuts, edit_shortcuts);
139 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
153 return build_div('<h4>Edit Mode (press <code>Enter</code> to enable)</h4>', cm_shortcuts);
140 };
154 };
@@ -163,9 +177,8 b' var IPython = (function (IPython) {'
163 return div;
177 return div;
164 };
178 };
165
179
166 // Set module variables
180 // Backwards compatability.
167 IPython.QuickHelp = QuickHelp;
181 IPython.QuickHelp = QuickHelp;
168
182
169 return IPython;
183 return {'QuickHelp': QuickHelp};
170
184 });
171 }(IPython));
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file chmod 100755 => 100644
NO CONTENT: modified file chmod 100755 => 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now