Show More
The requested changes are too big and content was truncated. Show full diff
This diff has been collapsed as it changes many lines, (531 lines changed) Show them Hide them | |||
@@ -0,0 +1,531 b'' | |||
|
1 | """A contents manager that uses the local file system for storage.""" | |
|
2 | ||
|
3 | # Copyright (c) IPython Development Team. | |
|
4 | # Distributed under the terms of the Modified BSD License. | |
|
5 | ||
|
6 | import base64 | |
|
7 | import io | |
|
8 | import os | |
|
9 | import glob | |
|
10 | import shutil | |
|
11 | ||
|
12 | from tornado import web | |
|
13 | ||
|
14 | from .manager import ContentsManager | |
|
15 | from IPython.nbformat import current | |
|
16 | from IPython.utils.path import ensure_dir_exists | |
|
17 | from IPython.utils.traitlets import Unicode, Bool, TraitError | |
|
18 | from IPython.utils.py3compat import getcwd | |
|
19 | from IPython.utils import tz | |
|
20 | from IPython.html.utils import is_hidden, to_os_path, url_path_join | |
|
21 | ||
|
22 | ||
|
23 | class FileContentsManager(ContentsManager): | |
|
24 | ||
|
25 | root_dir = Unicode(getcwd(), config=True) | |
|
26 | ||
|
27 | save_script = Bool(False, config=True, help='DEPRECATED, IGNORED') | |
|
28 | def _save_script_changed(self): | |
|
29 | self.log.warn(""" | |
|
30 | Automatically saving notebooks as scripts has been removed. | |
|
31 | Use `ipython nbconvert --to python [notebook]` instead. | |
|
32 | """) | |
|
33 | ||
|
34 | def _root_dir_changed(self, name, old, new): | |
|
35 | """Do a bit of validation of the root_dir.""" | |
|
36 | if not os.path.isabs(new): | |
|
37 | # If we receive a non-absolute path, make it absolute. | |
|
38 | self.root_dir = os.path.abspath(new) | |
|
39 | return | |
|
40 | if not os.path.isdir(new): | |
|
41 | raise TraitError("%r is not a directory" % new) | |
|
42 | ||
|
43 | checkpoint_dir = Unicode('.ipynb_checkpoints', config=True, | |
|
44 | help="""The directory name in which to keep file checkpoints | |
|
45 | ||
|
46 | This is a path relative to the file's own directory. | |
|
47 | ||
|
48 | By default, it is .ipynb_checkpoints | |
|
49 | """ | |
|
50 | ) | |
|
51 | ||
|
52 | def _copy(self, src, dest): | |
|
53 | """copy src to dest | |
|
54 | ||
|
55 | like shutil.copy2, but log errors in copystat | |
|
56 | """ | |
|
57 | shutil.copyfile(src, dest) | |
|
58 | try: | |
|
59 | shutil.copystat(src, dest) | |
|
60 | except OSError as e: | |
|
61 | self.log.debug("copystat on %s failed", dest, exc_info=True) | |
|
62 | ||
|
63 | def _get_os_path(self, name=None, path=''): | |
|
64 | """Given a filename and API path, return its file system | |
|
65 | path. | |
|
66 | ||
|
67 | Parameters | |
|
68 | ---------- | |
|
69 | name : string | |
|
70 | A filename | |
|
71 | path : string | |
|
72 | The relative API path to the named file. | |
|
73 | ||
|
74 | Returns | |
|
75 | ------- | |
|
76 | path : string | |
|
77 | API path to be evaluated relative to root_dir. | |
|
78 | """ | |
|
79 | if name is not None: | |
|
80 | path = url_path_join(path, name) | |
|
81 | return to_os_path(path, self.root_dir) | |
|
82 | ||
|
83 | def path_exists(self, path): | |
|
84 | """Does the API-style path refer to an extant directory? | |
|
85 | ||
|
86 | API-style wrapper for os.path.isdir | |
|
87 | ||
|
88 | Parameters | |
|
89 | ---------- | |
|
90 | path : string | |
|
91 | The path to check. This is an API path (`/` separated, | |
|
92 | relative to root_dir). | |
|
93 | ||
|
94 | Returns | |
|
95 | ------- | |
|
96 | exists : bool | |
|
97 | Whether the path is indeed a directory. | |
|
98 | """ | |
|
99 | path = path.strip('/') | |
|
100 | os_path = self._get_os_path(path=path) | |
|
101 | return os.path.isdir(os_path) | |
|
102 | ||
|
103 | def is_hidden(self, path): | |
|
104 | """Does the API style path correspond to a hidden directory or file? | |
|
105 | ||
|
106 | Parameters | |
|
107 | ---------- | |
|
108 | path : string | |
|
109 | The path to check. This is an API path (`/` separated, | |
|
110 | relative to root_dir). | |
|
111 | ||
|
112 | Returns | |
|
113 | ------- | |
|
114 | exists : bool | |
|
115 | Whether the path is hidden. | |
|
116 | ||
|
117 | """ | |
|
118 | path = path.strip('/') | |
|
119 | os_path = self._get_os_path(path=path) | |
|
120 | return is_hidden(os_path, self.root_dir) | |
|
121 | ||
|
122 | def file_exists(self, name, path=''): | |
|
123 | """Returns True if the file exists, else returns False. | |
|
124 | ||
|
125 | API-style wrapper for os.path.isfile | |
|
126 | ||
|
127 | Parameters | |
|
128 | ---------- | |
|
129 | name : string | |
|
130 | The name of the file you are checking. | |
|
131 | path : string | |
|
132 | The relative path to the file's directory (with '/' as separator) | |
|
133 | ||
|
134 | Returns | |
|
135 | ------- | |
|
136 | exists : bool | |
|
137 | Whether the file exists. | |
|
138 | """ | |
|
139 | path = path.strip('/') | |
|
140 | nbpath = self._get_os_path(name, path=path) | |
|
141 | return os.path.isfile(nbpath) | |
|
142 | ||
|
143 | def exists(self, name=None, path=''): | |
|
144 | """Returns True if the path [and name] exists, else returns False. | |
|
145 | ||
|
146 | API-style wrapper for os.path.exists | |
|
147 | ||
|
148 | Parameters | |
|
149 | ---------- | |
|
150 | name : string | |
|
151 | The name of the file you are checking. | |
|
152 | path : string | |
|
153 | The relative path to the file's directory (with '/' as separator) | |
|
154 | ||
|
155 | Returns | |
|
156 | ------- | |
|
157 | exists : bool | |
|
158 | Whether the target exists. | |
|
159 | """ | |
|
160 | path = path.strip('/') | |
|
161 | os_path = self._get_os_path(name, path=path) | |
|
162 | return os.path.exists(os_path) | |
|
163 | ||
|
164 | def _base_model(self, name, path=''): | |
|
165 | """Build the common base of a contents model""" | |
|
166 | os_path = self._get_os_path(name, path) | |
|
167 | info = os.stat(os_path) | |
|
168 | last_modified = tz.utcfromtimestamp(info.st_mtime) | |
|
169 | created = tz.utcfromtimestamp(info.st_ctime) | |
|
170 | # Create the base model. | |
|
171 | model = {} | |
|
172 | model['name'] = name | |
|
173 | model['path'] = path | |
|
174 | model['last_modified'] = last_modified | |
|
175 | model['created'] = created | |
|
176 | model['content'] = None | |
|
177 | model['format'] = None | |
|
178 | return model | |
|
179 | ||
|
180 | def _dir_model(self, name, path='', content=True): | |
|
181 | """Build a model for a directory | |
|
182 | ||
|
183 | if content is requested, will include a listing of the directory | |
|
184 | """ | |
|
185 | os_path = self._get_os_path(name, path) | |
|
186 | ||
|
187 | four_o_four = u'directory does not exist: %r' % os_path | |
|
188 | ||
|
189 | if not os.path.isdir(os_path): | |
|
190 | raise web.HTTPError(404, four_o_four) | |
|
191 | elif is_hidden(os_path, self.root_dir): | |
|
192 | self.log.info("Refusing to serve hidden directory %r, via 404 Error", | |
|
193 | os_path | |
|
194 | ) | |
|
195 | raise web.HTTPError(404, four_o_four) | |
|
196 | ||
|
197 | if name is None: | |
|
198 | if '/' in path: | |
|
199 | path, name = path.rsplit('/', 1) | |
|
200 | else: | |
|
201 | name = '' | |
|
202 | model = self._base_model(name, path) | |
|
203 | model['type'] = 'directory' | |
|
204 | dir_path = u'{}/{}'.format(path, name) | |
|
205 | if content: | |
|
206 | model['content'] = contents = [] | |
|
207 | for os_path in glob.glob(self._get_os_path('*', dir_path)): | |
|
208 | name = os.path.basename(os_path) | |
|
209 | if self.should_list(name) and not is_hidden(os_path, self.root_dir): | |
|
210 | contents.append(self.get_model(name=name, path=dir_path, content=False)) | |
|
211 | ||
|
212 | model['format'] = 'json' | |
|
213 | ||
|
214 | return model | |
|
215 | ||
|
216 | def _file_model(self, name, path='', content=True): | |
|
217 | """Build a model for a file | |
|
218 | ||
|
219 | if content is requested, include the file contents. | |
|
220 | UTF-8 text files will be unicode, binary files will be base64-encoded. | |
|
221 | """ | |
|
222 | model = self._base_model(name, path) | |
|
223 | model['type'] = 'file' | |
|
224 | if content: | |
|
225 | os_path = self._get_os_path(name, path) | |
|
226 | with io.open(os_path, 'rb') as f: | |
|
227 | bcontent = f.read() | |
|
228 | try: | |
|
229 | model['content'] = bcontent.decode('utf8') | |
|
230 | except UnicodeError as e: | |
|
231 | model['content'] = base64.encodestring(bcontent).decode('ascii') | |
|
232 | model['format'] = 'base64' | |
|
233 | else: | |
|
234 | model['format'] = 'text' | |
|
235 | return model | |
|
236 | ||
|
237 | ||
|
238 | def _notebook_model(self, name, path='', content=True): | |
|
239 | """Build a notebook model | |
|
240 | ||
|
241 | if content is requested, the notebook content will be populated | |
|
242 | as a JSON structure (not double-serialized) | |
|
243 | """ | |
|
244 | model = self._base_model(name, path) | |
|
245 | model['type'] = 'notebook' | |
|
246 | if content: | |
|
247 | os_path = self._get_os_path(name, path) | |
|
248 | with io.open(os_path, 'r', encoding='utf-8') as f: | |
|
249 | try: | |
|
250 | nb = current.read(f, u'json') | |
|
251 | except Exception as e: | |
|
252 | raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e)) | |
|
253 | self.mark_trusted_cells(nb, name, path) | |
|
254 | model['content'] = nb | |
|
255 | model['format'] = 'json' | |
|
256 | return model | |
|
257 | ||
|
258 | def get_model(self, name, path='', content=True): | |
|
259 | """ Takes a path and name for an entity and returns its model | |
|
260 | ||
|
261 | Parameters | |
|
262 | ---------- | |
|
263 | name : str | |
|
264 | the name of the target | |
|
265 | path : str | |
|
266 | the API path that describes the relative path for the target | |
|
267 | ||
|
268 | Returns | |
|
269 | ------- | |
|
270 | model : dict | |
|
271 | the contents model. If content=True, returns the contents | |
|
272 | of the file or directory as well. | |
|
273 | """ | |
|
274 | path = path.strip('/') | |
|
275 | ||
|
276 | if not self.exists(name=name, path=path): | |
|
277 | raise web.HTTPError(404, u'No such file or directory: %s/%s' % (path, name)) | |
|
278 | ||
|
279 | os_path = self._get_os_path(name, path) | |
|
280 | if os.path.isdir(os_path): | |
|
281 | model = self._dir_model(name, path, content) | |
|
282 | elif name.endswith('.ipynb'): | |
|
283 | model = self._notebook_model(name, path, content) | |
|
284 | else: | |
|
285 | model = self._file_model(name, path, content) | |
|
286 | return model | |
|
287 | ||
|
288 | def _save_notebook(self, os_path, model, name='', path=''): | |
|
289 | """save a notebook file""" | |
|
290 | # Save the notebook file | |
|
291 | nb = current.to_notebook_json(model['content']) | |
|
292 | ||
|
293 | self.check_and_sign(nb, name, path) | |
|
294 | ||
|
295 | if 'name' in nb['metadata']: | |
|
296 | nb['metadata']['name'] = u'' | |
|
297 | ||
|
298 | with io.open(os_path, 'w', encoding='utf-8') as f: | |
|
299 | current.write(nb, f, u'json') | |
|
300 | ||
|
301 | def _save_file(self, os_path, model, name='', path=''): | |
|
302 | """save a non-notebook file""" | |
|
303 | fmt = model.get('format', None) | |
|
304 | if fmt not in {'text', 'base64'}: | |
|
305 | raise web.HTTPError(400, "Must specify format of file contents as 'text' or 'base64'") | |
|
306 | try: | |
|
307 | content = model['content'] | |
|
308 | if fmt == 'text': | |
|
309 | bcontent = content.encode('utf8') | |
|
310 | else: | |
|
311 | b64_bytes = content.encode('ascii') | |
|
312 | bcontent = base64.decodestring(b64_bytes) | |
|
313 | except Exception as e: | |
|
314 | raise web.HTTPError(400, u'Encoding error saving %s: %s' % (os_path, e)) | |
|
315 | with io.open(os_path, 'wb') as f: | |
|
316 | f.write(bcontent) | |
|
317 | ||
|
318 | def _save_directory(self, os_path, model, name='', path=''): | |
|
319 | """create a directory""" | |
|
320 | if is_hidden(os_path, self.root_dir): | |
|
321 | raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path) | |
|
322 | if not os.path.exists(os_path): | |
|
323 | os.mkdir(os_path) | |
|
324 | elif not os.path.isdir(os_path): | |
|
325 | raise web.HTTPError(400, u'Not a directory: %s' % (os_path)) | |
|
326 | else: | |
|
327 | self.log.debug("Directory %r already exists", os_path) | |
|
328 | ||
|
329 | def save(self, model, name='', path=''): | |
|
330 | """Save the file model and return the model with no content.""" | |
|
331 | path = path.strip('/') | |
|
332 | ||
|
333 | if 'type' not in model: | |
|
334 | raise web.HTTPError(400, u'No file type provided') | |
|
335 | if 'content' not in model and model['type'] != 'directory': | |
|
336 | raise web.HTTPError(400, u'No file content provided') | |
|
337 | ||
|
338 | # One checkpoint should always exist | |
|
339 | if self.file_exists(name, path) and not self.list_checkpoints(name, path): | |
|
340 | self.create_checkpoint(name, path) | |
|
341 | ||
|
342 | new_path = model.get('path', path).strip('/') | |
|
343 | new_name = model.get('name', name) | |
|
344 | ||
|
345 | if path != new_path or name != new_name: | |
|
346 | self.rename(name, path, new_name, new_path) | |
|
347 | ||
|
348 | os_path = self._get_os_path(new_name, new_path) | |
|
349 | self.log.debug("Saving %s", os_path) | |
|
350 | try: | |
|
351 | if model['type'] == 'notebook': | |
|
352 | self._save_notebook(os_path, model, new_name, new_path) | |
|
353 | elif model['type'] == 'file': | |
|
354 | self._save_file(os_path, model, new_name, new_path) | |
|
355 | elif model['type'] == 'directory': | |
|
356 | self._save_directory(os_path, model, new_name, new_path) | |
|
357 | else: | |
|
358 | raise web.HTTPError(400, "Unhandled contents type: %s" % model['type']) | |
|
359 | except web.HTTPError: | |
|
360 | raise | |
|
361 | except Exception as e: | |
|
362 | raise web.HTTPError(400, u'Unexpected error while saving file: %s %s' % (os_path, e)) | |
|
363 | ||
|
364 | model = self.get_model(new_name, new_path, content=False) | |
|
365 | return model | |
|
366 | ||
|
367 | def update(self, model, name, path=''): | |
|
368 | """Update the file's path and/or name | |
|
369 | ||
|
370 | For use in PATCH requests, to enable renaming a file without | |
|
371 | re-uploading its contents. Only used for renaming at the moment. | |
|
372 | """ | |
|
373 | path = path.strip('/') | |
|
374 | new_name = model.get('name', name) | |
|
375 | new_path = model.get('path', path).strip('/') | |
|
376 | if path != new_path or name != new_name: | |
|
377 | self.rename(name, path, new_name, new_path) | |
|
378 | model = self.get_model(new_name, new_path, content=False) | |
|
379 | return model | |
|
380 | ||
|
381 | def delete(self, name, path=''): | |
|
382 | """Delete file by name and path.""" | |
|
383 | path = path.strip('/') | |
|
384 | os_path = self._get_os_path(name, path) | |
|
385 | rm = os.unlink | |
|
386 | if os.path.isdir(os_path): | |
|
387 | listing = os.listdir(os_path) | |
|
388 | # don't delete non-empty directories (checkpoints dir doesn't count) | |
|
389 | if listing and listing != [self.checkpoint_dir]: | |
|
390 | raise web.HTTPError(400, u'Directory %s not empty' % os_path) | |
|
391 | elif not os.path.isfile(os_path): | |
|
392 | raise web.HTTPError(404, u'File does not exist: %s' % os_path) | |
|
393 | ||
|
394 | # clear checkpoints | |
|
395 | for checkpoint in self.list_checkpoints(name, path): | |
|
396 | checkpoint_id = checkpoint['id'] | |
|
397 | cp_path = self.get_checkpoint_path(checkpoint_id, name, path) | |
|
398 | if os.path.isfile(cp_path): | |
|
399 | self.log.debug("Unlinking checkpoint %s", cp_path) | |
|
400 | os.unlink(cp_path) | |
|
401 | ||
|
402 | if os.path.isdir(os_path): | |
|
403 | self.log.debug("Removing directory %s", os_path) | |
|
404 | shutil.rmtree(os_path) | |
|
405 | else: | |
|
406 | self.log.debug("Unlinking file %s", os_path) | |
|
407 | rm(os_path) | |
|
408 | ||
|
409 | def rename(self, old_name, old_path, new_name, new_path): | |
|
410 | """Rename a file.""" | |
|
411 | old_path = old_path.strip('/') | |
|
412 | new_path = new_path.strip('/') | |
|
413 | if new_name == old_name and new_path == old_path: | |
|
414 | return | |
|
415 | ||
|
416 | new_os_path = self._get_os_path(new_name, new_path) | |
|
417 | old_os_path = self._get_os_path(old_name, old_path) | |
|
418 | ||
|
419 | # Should we proceed with the move? | |
|
420 | if os.path.isfile(new_os_path): | |
|
421 | raise web.HTTPError(409, u'File with name already exists: %s' % new_os_path) | |
|
422 | ||
|
423 | # Move the file | |
|
424 | try: | |
|
425 | shutil.move(old_os_path, new_os_path) | |
|
426 | except Exception as e: | |
|
427 | raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_os_path, e)) | |
|
428 | ||
|
429 | # Move the checkpoints | |
|
430 | old_checkpoints = self.list_checkpoints(old_name, old_path) | |
|
431 | for cp in old_checkpoints: | |
|
432 | checkpoint_id = cp['id'] | |
|
433 | old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path) | |
|
434 | new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path) | |
|
435 | if os.path.isfile(old_cp_path): | |
|
436 | self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path) | |
|
437 | shutil.move(old_cp_path, new_cp_path) | |
|
438 | ||
|
439 | # Checkpoint-related utilities | |
|
440 | ||
|
441 | def get_checkpoint_path(self, checkpoint_id, name, path=''): | |
|
442 | """find the path to a checkpoint""" | |
|
443 | path = path.strip('/') | |
|
444 | basename, ext = os.path.splitext(name) | |
|
445 | filename = u"{name}-{checkpoint_id}{ext}".format( | |
|
446 | name=basename, | |
|
447 | checkpoint_id=checkpoint_id, | |
|
448 | ext=ext, | |
|
449 | ) | |
|
450 | os_path = self._get_os_path(path=path) | |
|
451 | cp_dir = os.path.join(os_path, self.checkpoint_dir) | |
|
452 | ensure_dir_exists(cp_dir) | |
|
453 | cp_path = os.path.join(cp_dir, filename) | |
|
454 | return cp_path | |
|
455 | ||
|
456 | def get_checkpoint_model(self, checkpoint_id, name, path=''): | |
|
457 | """construct the info dict for a given checkpoint""" | |
|
458 | path = path.strip('/') | |
|
459 | cp_path = self.get_checkpoint_path(checkpoint_id, name, path) | |
|
460 | stats = os.stat(cp_path) | |
|
461 | last_modified = tz.utcfromtimestamp(stats.st_mtime) | |
|
462 | info = dict( | |
|
463 | id = checkpoint_id, | |
|
464 | last_modified = last_modified, | |
|
465 | ) | |
|
466 | return info | |
|
467 | ||
|
468 | # public checkpoint API | |
|
469 | ||
|
470 | def create_checkpoint(self, name, path=''): | |
|
471 | """Create a checkpoint from the current state of a file""" | |
|
472 | path = path.strip('/') | |
|
473 | src_path = self._get_os_path(name, path) | |
|
474 | # only the one checkpoint ID: | |
|
475 | checkpoint_id = u"checkpoint" | |
|
476 | cp_path = self.get_checkpoint_path(checkpoint_id, name, path) | |
|
477 | self.log.debug("creating checkpoint for %s", name) | |
|
478 | self._copy(src_path, cp_path) | |
|
479 | ||
|
480 | # return the checkpoint info | |
|
481 | return self.get_checkpoint_model(checkpoint_id, name, path) | |
|
482 | ||
|
483 | def list_checkpoints(self, name, path=''): | |
|
484 | """list the checkpoints for a given file | |
|
485 | ||
|
486 | This contents manager currently only supports one checkpoint per file. | |
|
487 | """ | |
|
488 | path = path.strip('/') | |
|
489 | checkpoint_id = "checkpoint" | |
|
490 | os_path = self.get_checkpoint_path(checkpoint_id, name, path) | |
|
491 | if not os.path.exists(os_path): | |
|
492 | return [] | |
|
493 | else: | |
|
494 | return [self.get_checkpoint_model(checkpoint_id, name, path)] | |
|
495 | ||
|
496 | ||
|
497 | def restore_checkpoint(self, checkpoint_id, name, path=''): | |
|
498 | """restore a file to a checkpointed state""" | |
|
499 | path = path.strip('/') | |
|
500 | self.log.info("restoring %s from checkpoint %s", name, checkpoint_id) | |
|
501 | nb_path = self._get_os_path(name, path) | |
|
502 | cp_path = self.get_checkpoint_path(checkpoint_id, name, path) | |
|
503 | if not os.path.isfile(cp_path): | |
|
504 | self.log.debug("checkpoint file does not exist: %s", cp_path) | |
|
505 | raise web.HTTPError(404, | |
|
506 | u'checkpoint does not exist: %s-%s' % (name, checkpoint_id) | |
|
507 | ) | |
|
508 | # ensure notebook is readable (never restore from an unreadable notebook) | |
|
509 | if cp_path.endswith('.ipynb'): | |
|
510 | with io.open(cp_path, 'r', encoding='utf-8') as f: | |
|
511 | current.read(f, u'json') | |
|
512 | self._copy(cp_path, nb_path) | |
|
513 | self.log.debug("copying %s -> %s", cp_path, nb_path) | |
|
514 | ||
|
515 | def delete_checkpoint(self, checkpoint_id, name, path=''): | |
|
516 | """delete a file's checkpoint""" | |
|
517 | path = path.strip('/') | |
|
518 | cp_path = self.get_checkpoint_path(checkpoint_id, name, path) | |
|
519 | if not os.path.isfile(cp_path): | |
|
520 | raise web.HTTPError(404, | |
|
521 | u'Checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id) | |
|
522 | ) | |
|
523 | self.log.debug("unlinking %s", cp_path) | |
|
524 | os.unlink(cp_path) | |
|
525 | ||
|
526 | def info_string(self): | |
|
527 | return "Serving notebooks from local directory: %s" % self.root_dir | |
|
528 | ||
|
529 | def get_kernel_path(self, name, path='', model=None): | |
|
530 | """Return the initial working dir a kernel associated with a given notebook""" | |
|
531 | return os.path.join(self.root_dir, path) |
@@ -0,0 +1,286 b'' | |||
|
1 | """Tornado handlers for the contents web service.""" | |
|
2 | ||
|
3 | # Copyright (c) IPython Development Team. | |
|
4 | # Distributed under the terms of the Modified BSD License. | |
|
5 | ||
|
6 | import json | |
|
7 | ||
|
8 | from tornado import web | |
|
9 | ||
|
10 | from IPython.html.utils import url_path_join, url_escape | |
|
11 | from IPython.utils.jsonutil import date_default | |
|
12 | ||
|
13 | from IPython.html.base.handlers import (IPythonHandler, json_errors, | |
|
14 | file_path_regex, path_regex, | |
|
15 | file_name_regex) | |
|
16 | ||
|
17 | ||
|
18 | def sort_key(model): | |
|
19 | """key function for case-insensitive sort by name and type""" | |
|
20 | iname = model['name'].lower() | |
|
21 | type_key = { | |
|
22 | 'directory' : '0', | |
|
23 | 'notebook' : '1', | |
|
24 | 'file' : '2', | |
|
25 | }.get(model['type'], '9') | |
|
26 | return u'%s%s' % (type_key, iname) | |
|
27 | ||
|
28 | class ContentsHandler(IPythonHandler): | |
|
29 | ||
|
30 | SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE') | |
|
31 | ||
|
32 | def location_url(self, name, path): | |
|
33 | """Return the full URL location of a file. | |
|
34 | ||
|
35 | Parameters | |
|
36 | ---------- | |
|
37 | name : unicode | |
|
38 | The base name of the file, such as "foo.ipynb". | |
|
39 | path : unicode | |
|
40 | The API path of the file, such as "foo/bar". | |
|
41 | """ | |
|
42 | return url_escape(url_path_join( | |
|
43 | self.base_url, 'api', 'contents', path, name | |
|
44 | )) | |
|
45 | ||
|
46 | def _finish_model(self, model, location=True): | |
|
47 | """Finish a JSON request with a model, setting relevant headers, etc.""" | |
|
48 | if location: | |
|
49 | location = self.location_url(model['name'], model['path']) | |
|
50 | self.set_header('Location', location) | |
|
51 | self.set_header('Last-Modified', model['last_modified']) | |
|
52 | self.finish(json.dumps(model, default=date_default)) | |
|
53 | ||
|
54 | @web.authenticated | |
|
55 | @json_errors | |
|
56 | def get(self, path='', name=None): | |
|
57 | """Return a model for a file or directory. | |
|
58 | ||
|
59 | A directory model contains a list of models (without content) | |
|
60 | of the files and directories it contains. | |
|
61 | """ | |
|
62 | path = path or '' | |
|
63 | model = self.contents_manager.get_model(name=name, path=path) | |
|
64 | if model['type'] == 'directory': | |
|
65 | # group listing by type, then by name (case-insensitive) | |
|
66 | # FIXME: sorting should be done in the frontends | |
|
67 | model['content'].sort(key=sort_key) | |
|
68 | self._finish_model(model, location=False) | |
|
69 | ||
|
70 | @web.authenticated | |
|
71 | @json_errors | |
|
72 | def patch(self, path='', name=None): | |
|
73 | """PATCH renames a notebook without re-uploading content.""" | |
|
74 | cm = self.contents_manager | |
|
75 | if name is None: | |
|
76 | raise web.HTTPError(400, u'Filename missing') | |
|
77 | model = self.get_json_body() | |
|
78 | if model is None: | |
|
79 | raise web.HTTPError(400, u'JSON body missing') | |
|
80 | model = cm.update(model, name, path) | |
|
81 | self._finish_model(model) | |
|
82 | ||
|
83 | def _copy(self, copy_from, path, copy_to=None): | |
|
84 | """Copy a file, optionally specifying the new name. | |
|
85 | """ | |
|
86 | self.log.info(u"Copying {copy_from} to {path}/{copy_to}".format( | |
|
87 | copy_from=copy_from, | |
|
88 | path=path, | |
|
89 | copy_to=copy_to or '', | |
|
90 | )) | |
|
91 | model = self.contents_manager.copy(copy_from, copy_to, path) | |
|
92 | self.set_status(201) | |
|
93 | self._finish_model(model) | |
|
94 | ||
|
95 | def _upload(self, model, path, name=None): | |
|
96 | """Handle upload of a new file | |
|
97 | ||
|
98 | If name specified, create it in path/name, | |
|
99 | otherwise create a new untitled file in path. | |
|
100 | """ | |
|
101 | self.log.info(u"Uploading file to %s/%s", path, name or '') | |
|
102 | if name: | |
|
103 | model['name'] = name | |
|
104 | ||
|
105 | model = self.contents_manager.create_file(model, path) | |
|
106 | self.set_status(201) | |
|
107 | self._finish_model(model) | |
|
108 | ||
|
109 | def _create_empty_file(self, path, name=None, ext='.ipynb'): | |
|
110 | """Create an empty file in path | |
|
111 | ||
|
112 | If name specified, create it in path/name. | |
|
113 | """ | |
|
114 | self.log.info(u"Creating new file in %s/%s", path, name or '') | |
|
115 | model = {} | |
|
116 | if name: | |
|
117 | model['name'] = name | |
|
118 | model = self.contents_manager.create_file(model, path=path, ext=ext) | |
|
119 | self.set_status(201) | |
|
120 | self._finish_model(model) | |
|
121 | ||
|
122 | def _save(self, model, path, name): | |
|
123 | """Save an existing file.""" | |
|
124 | self.log.info(u"Saving file at %s/%s", path, name) | |
|
125 | model = self.contents_manager.save(model, name, path) | |
|
126 | if model['path'] != path.strip('/') or model['name'] != name: | |
|
127 | # a rename happened, set Location header | |
|
128 | location = True | |
|
129 | else: | |
|
130 | location = False | |
|
131 | self._finish_model(model, location) | |
|
132 | ||
|
133 | @web.authenticated | |
|
134 | @json_errors | |
|
135 | def post(self, path='', name=None): | |
|
136 | """Create a new file or directory in the specified path. | |
|
137 | ||
|
138 | POST creates new files or directories. The server always decides on the name. | |
|
139 | ||
|
140 | POST /api/contents/path | |
|
141 | New untitled notebook in path. If content specified, upload a | |
|
142 | notebook, otherwise start empty. | |
|
143 | POST /api/contents/path | |
|
144 | with body {"copy_from" : "OtherNotebook.ipynb"} | |
|
145 | New copy of OtherNotebook in path | |
|
146 | """ | |
|
147 | ||
|
148 | if name is not None: | |
|
149 | path = u'{}/{}'.format(path, name) | |
|
150 | ||
|
151 | cm = self.contents_manager | |
|
152 | ||
|
153 | if cm.file_exists(path): | |
|
154 | raise web.HTTPError(400, "Cannot POST to existing files, use PUT instead.") | |
|
155 | ||
|
156 | if not cm.path_exists(path): | |
|
157 | raise web.HTTPError(404, "No such directory: %s" % path) | |
|
158 | ||
|
159 | model = self.get_json_body() | |
|
160 | ||
|
161 | if model is not None: | |
|
162 | copy_from = model.get('copy_from') | |
|
163 | ext = model.get('ext', '.ipynb') | |
|
164 | if model.get('content') is not None: | |
|
165 | if copy_from: | |
|
166 | raise web.HTTPError(400, "Can't upload and copy at the same time.") | |
|
167 | self._upload(model, path) | |
|
168 | elif copy_from: | |
|
169 | self._copy(copy_from, path) | |
|
170 | else: | |
|
171 | self._create_empty_file(path, ext=ext) | |
|
172 | else: | |
|
173 | self._create_empty_file(path) | |
|
174 | ||
|
175 | @web.authenticated | |
|
176 | @json_errors | |
|
177 | def put(self, path='', name=None): | |
|
178 | """Saves the file in the location specified by name and path. | |
|
179 | ||
|
180 | PUT is very similar to POST, but the requester specifies the name, | |
|
181 | whereas with POST, the server picks the name. | |
|
182 | ||
|
183 | PUT /api/contents/path/Name.ipynb | |
|
184 | Save notebook at ``path/Name.ipynb``. Notebook structure is specified | |
|
185 | in `content` key of JSON request body. If content is not specified, | |
|
186 | create a new empty notebook. | |
|
187 | PUT /api/contents/path/Name.ipynb | |
|
188 | with JSON body:: | |
|
189 | ||
|
190 | { | |
|
191 | "copy_from" : "[path/to/]OtherNotebook.ipynb" | |
|
192 | } | |
|
193 | ||
|
194 | Copy OtherNotebook to Name | |
|
195 | """ | |
|
196 | if name is None: | |
|
197 | raise web.HTTPError(400, "name must be specified with PUT.") | |
|
198 | ||
|
199 | model = self.get_json_body() | |
|
200 | if model: | |
|
201 | copy_from = model.get('copy_from') | |
|
202 | if copy_from: | |
|
203 | if model.get('content'): | |
|
204 | raise web.HTTPError(400, "Can't upload and copy at the same time.") | |
|
205 | self._copy(copy_from, path, name) | |
|
206 | elif self.contents_manager.file_exists(name, path): | |
|
207 | self._save(model, path, name) | |
|
208 | else: | |
|
209 | self._upload(model, path, name) | |
|
210 | else: | |
|
211 | self._create_empty_file(path, name) | |
|
212 | ||
|
213 | @web.authenticated | |
|
214 | @json_errors | |
|
215 | def delete(self, path='', name=None): | |
|
216 | """delete a file in the given path""" | |
|
217 | cm = self.contents_manager | |
|
218 | self.log.warn('delete %s:%s', path, name) | |
|
219 | cm.delete(name, path) | |
|
220 | self.set_status(204) | |
|
221 | self.finish() | |
|
222 | ||
|
223 | ||
|
224 | class CheckpointsHandler(IPythonHandler): | |
|
225 | ||
|
226 | SUPPORTED_METHODS = ('GET', 'POST') | |
|
227 | ||
|
228 | @web.authenticated | |
|
229 | @json_errors | |
|
230 | def get(self, path='', name=None): | |
|
231 | """get lists checkpoints for a file""" | |
|
232 | cm = self.contents_manager | |
|
233 | checkpoints = cm.list_checkpoints(name, path) | |
|
234 | data = json.dumps(checkpoints, default=date_default) | |
|
235 | self.finish(data) | |
|
236 | ||
|
237 | @web.authenticated | |
|
238 | @json_errors | |
|
239 | def post(self, path='', name=None): | |
|
240 | """post creates a new checkpoint""" | |
|
241 | cm = self.contents_manager | |
|
242 | checkpoint = cm.create_checkpoint(name, path) | |
|
243 | data = json.dumps(checkpoint, default=date_default) | |
|
244 | location = url_path_join(self.base_url, 'api/contents', | |
|
245 | path, name, 'checkpoints', checkpoint['id']) | |
|
246 | self.set_header('Location', url_escape(location)) | |
|
247 | self.set_status(201) | |
|
248 | self.finish(data) | |
|
249 | ||
|
250 | ||
|
251 | class ModifyCheckpointsHandler(IPythonHandler): | |
|
252 | ||
|
253 | SUPPORTED_METHODS = ('POST', 'DELETE') | |
|
254 | ||
|
255 | @web.authenticated | |
|
256 | @json_errors | |
|
257 | def post(self, path, name, checkpoint_id): | |
|
258 | """post restores a file from a checkpoint""" | |
|
259 | cm = self.contents_manager | |
|
260 | cm.restore_checkpoint(checkpoint_id, name, path) | |
|
261 | self.set_status(204) | |
|
262 | self.finish() | |
|
263 | ||
|
264 | @web.authenticated | |
|
265 | @json_errors | |
|
266 | def delete(self, path, name, checkpoint_id): | |
|
267 | """delete clears a checkpoint for a given file""" | |
|
268 | cm = self.contents_manager | |
|
269 | cm.delete_checkpoint(checkpoint_id, name, path) | |
|
270 | self.set_status(204) | |
|
271 | self.finish() | |
|
272 | ||
|
273 | #----------------------------------------------------------------------------- | |
|
274 | # URL to handler mappings | |
|
275 | #----------------------------------------------------------------------------- | |
|
276 | ||
|
277 | ||
|
278 | _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)" | |
|
279 | ||
|
280 | default_handlers = [ | |
|
281 | (r"/api/contents%s/checkpoints" % file_path_regex, CheckpointsHandler), | |
|
282 | (r"/api/contents%s/checkpoints/%s" % (file_path_regex, _checkpoint_id_regex), | |
|
283 | ModifyCheckpointsHandler), | |
|
284 | (r"/api/contents%s" % file_path_regex, ContentsHandler), | |
|
285 | (r"/api/contents%s" % path_regex, ContentsHandler), | |
|
286 | ] |
@@ -0,0 +1,333 b'' | |||
|
1 | """A base class for contents managers.""" | |
|
2 | ||
|
3 | # Copyright (c) IPython Development Team. | |
|
4 | # Distributed under the terms of the Modified BSD License. | |
|
5 | ||
|
6 | from fnmatch import fnmatch | |
|
7 | import itertools | |
|
8 | import os | |
|
9 | ||
|
10 | from tornado.web import HTTPError | |
|
11 | ||
|
12 | from IPython.config.configurable import LoggingConfigurable | |
|
13 | from IPython.nbformat import current, sign | |
|
14 | from IPython.utils.traitlets import Instance, Unicode, List | |
|
15 | ||
|
16 | ||
|
17 | class ContentsManager(LoggingConfigurable): | |
|
18 | """Base class for serving files and directories. | |
|
19 | ||
|
20 | This serves any text or binary file, | |
|
21 | as well as directories, | |
|
22 | with special handling for JSON notebook documents. | |
|
23 | ||
|
24 | Most APIs take a path argument, | |
|
25 | which is always an API-style unicode path, | |
|
26 | and always refers to a directory. | |
|
27 | ||
|
28 | - unicode, not url-escaped | |
|
29 | - '/'-separated | |
|
30 | - leading and trailing '/' will be stripped | |
|
31 | - if unspecified, path defaults to '', | |
|
32 | indicating the root path. | |
|
33 | ||
|
34 | name is also unicode, and refers to a specfic target: | |
|
35 | ||
|
36 | - unicode, not url-escaped | |
|
37 | - must not contain '/' | |
|
38 | - It refers to an individual filename | |
|
39 | - It may refer to a directory name, | |
|
40 | in the case of listing or creating directories. | |
|
41 | ||
|
42 | """ | |
|
43 | ||
|
44 | notary = Instance(sign.NotebookNotary) | |
|
45 | def _notary_default(self): | |
|
46 | return sign.NotebookNotary(parent=self) | |
|
47 | ||
|
48 | hide_globs = List(Unicode, [ | |
|
49 | u'__pycache__', '*.pyc', '*.pyo', | |
|
50 | '.DS_Store', '*.so', '*.dylib', '*~', | |
|
51 | ], config=True, help=""" | |
|
52 | Glob patterns to hide in file and directory listings. | |
|
53 | """) | |
|
54 | ||
|
55 | untitled_notebook = Unicode("Untitled", config=True, | |
|
56 | help="The base name used when creating untitled notebooks." | |
|
57 | ) | |
|
58 | ||
|
59 | untitled_file = Unicode("untitled", config=True, | |
|
60 | help="The base name used when creating untitled files." | |
|
61 | ) | |
|
62 | ||
|
63 | untitled_directory = Unicode("Untitled Folder", config=True, | |
|
64 | help="The base name used when creating untitled directories." | |
|
65 | ) | |
|
66 | ||
|
67 | # ContentsManager API part 1: methods that must be | |
|
68 | # implemented in subclasses. | |
|
69 | ||
|
70 | def path_exists(self, path): | |
|
71 | """Does the API-style path (directory) actually exist? | |
|
72 | ||
|
73 | Like os.path.isdir | |
|
74 | ||
|
75 | Override this method in subclasses. | |
|
76 | ||
|
77 | Parameters | |
|
78 | ---------- | |
|
79 | path : string | |
|
80 | The path to check | |
|
81 | ||
|
82 | Returns | |
|
83 | ------- | |
|
84 | exists : bool | |
|
85 | Whether the path does indeed exist. | |
|
86 | """ | |
|
87 | raise NotImplementedError | |
|
88 | ||
|
89 | def is_hidden(self, path): | |
|
90 | """Does the API style path correspond to a hidden directory or file? | |
|
91 | ||
|
92 | Parameters | |
|
93 | ---------- | |
|
94 | path : string | |
|
95 | The path to check. This is an API path (`/` separated, | |
|
96 | relative to root dir). | |
|
97 | ||
|
98 | Returns | |
|
99 | ------- | |
|
100 | hidden : bool | |
|
101 | Whether the path is hidden. | |
|
102 | ||
|
103 | """ | |
|
104 | raise NotImplementedError | |
|
105 | ||
|
106 | def file_exists(self, name, path=''): | |
|
107 | """Does a file exist at the given name and path? | |
|
108 | ||
|
109 | Like os.path.isfile | |
|
110 | ||
|
111 | Override this method in subclasses. | |
|
112 | ||
|
113 | Parameters | |
|
114 | ---------- | |
|
115 | name : string | |
|
116 | The name of the file you are checking. | |
|
117 | path : string | |
|
118 | The relative path to the file's directory (with '/' as separator) | |
|
119 | ||
|
120 | Returns | |
|
121 | ------- | |
|
122 | exists : bool | |
|
123 | Whether the file exists. | |
|
124 | """ | |
|
125 | raise NotImplementedError('must be implemented in a subclass') | |
|
126 | ||
|
127 | def exists(self, name, path=''): | |
|
128 | """Does a file or directory exist at the given name and path? | |
|
129 | ||
|
130 | Like os.path.exists | |
|
131 | ||
|
132 | Parameters | |
|
133 | ---------- | |
|
134 | name : string | |
|
135 | The name of the file you are checking. | |
|
136 | path : string | |
|
137 | The relative path to the file's directory (with '/' as separator) | |
|
138 | ||
|
139 | Returns | |
|
140 | ------- | |
|
141 | exists : bool | |
|
142 | Whether the target exists. | |
|
143 | """ | |
|
144 | return self.file_exists(name, path) or self.path_exists("%s/%s" % (path, name)) | |
|
145 | ||
|
146 | def get_model(self, name, path='', content=True): | |
|
147 | """Get the model of a file or directory with or without content.""" | |
|
148 | raise NotImplementedError('must be implemented in a subclass') | |
|
149 | ||
|
150 | def save(self, model, name, path=''): | |
|
151 | """Save the file or directory and return the model with no content.""" | |
|
152 | raise NotImplementedError('must be implemented in a subclass') | |
|
153 | ||
|
154 | def update(self, model, name, path=''): | |
|
155 | """Update the file or directory and return the model with no content. | |
|
156 | ||
|
157 | For use in PATCH requests, to enable renaming a file without | |
|
158 | re-uploading its contents. Only used for renaming at the moment. | |
|
159 | """ | |
|
160 | raise NotImplementedError('must be implemented in a subclass') | |
|
161 | ||
|
162 | def delete(self, name, path=''): | |
|
163 | """Delete file or directory by name and path.""" | |
|
164 | raise NotImplementedError('must be implemented in a subclass') | |
|
165 | ||
|
166 | def create_checkpoint(self, name, path=''): | |
|
167 | """Create a checkpoint of the current state of a file | |
|
168 | ||
|
169 | Returns a checkpoint_id for the new checkpoint. | |
|
170 | """ | |
|
171 | raise NotImplementedError("must be implemented in a subclass") | |
|
172 | ||
|
173 | def list_checkpoints(self, name, path=''): | |
|
174 | """Return a list of checkpoints for a given file""" | |
|
175 | return [] | |
|
176 | ||
|
177 | def restore_checkpoint(self, checkpoint_id, name, path=''): | |
|
178 | """Restore a file from one of its checkpoints""" | |
|
179 | raise NotImplementedError("must be implemented in a subclass") | |
|
180 | ||
|
181 | def delete_checkpoint(self, checkpoint_id, name, path=''): | |
|
182 | """delete a checkpoint for a file""" | |
|
183 | raise NotImplementedError("must be implemented in a subclass") | |
|
184 | ||
|
185 | # ContentsManager API part 2: methods that have useable default | |
|
186 | # implementations, but can be overridden in subclasses. | |
|
187 | ||
|
188 | def info_string(self): | |
|
189 | return "Serving contents" | |
|
190 | ||
|
191 | def get_kernel_path(self, name, path='', model=None): | |
|
192 | """ Return the path to start kernel in """ | |
|
193 | return path | |
|
194 | ||
|
195 | def increment_filename(self, filename, path=''): | |
|
196 | """Increment a filename until it is unique. | |
|
197 | ||
|
198 | Parameters | |
|
199 | ---------- | |
|
200 | filename : unicode | |
|
201 | The name of a file, including extension | |
|
202 | path : unicode | |
|
203 | The API path of the target's directory | |
|
204 | ||
|
205 | Returns | |
|
206 | ------- | |
|
207 | name : unicode | |
|
208 | A filename that is unique, based on the input filename. | |
|
209 | """ | |
|
210 | path = path.strip('/') | |
|
211 | basename, ext = os.path.splitext(filename) | |
|
212 | for i in itertools.count(): | |
|
213 | name = u'{basename}{i}{ext}'.format(basename=basename, i=i, | |
|
214 | ext=ext) | |
|
215 | if not self.file_exists(name, path): | |
|
216 | break | |
|
217 | return name | |
|
218 | ||
|
219 | def create_file(self, model=None, path='', ext='.ipynb'): | |
|
220 | """Create a new file or directory and return its model with no content.""" | |
|
221 | path = path.strip('/') | |
|
222 | if model is None: | |
|
223 | model = {} | |
|
224 | if 'content' not in model and model.get('type', None) != 'directory': | |
|
225 | if ext == '.ipynb': | |
|
226 | metadata = current.new_metadata(name=u'') | |
|
227 | model['content'] = current.new_notebook(metadata=metadata) | |
|
228 | model['type'] = 'notebook' | |
|
229 | model['format'] = 'json' | |
|
230 | else: | |
|
231 | model['content'] = '' | |
|
232 | model['type'] = 'file' | |
|
233 | model['format'] = 'text' | |
|
234 | if 'name' not in model: | |
|
235 | if model['type'] == 'directory': | |
|
236 | untitled = self.untitled_directory | |
|
237 | elif model['type'] == 'notebook': | |
|
238 | untitled = self.untitled_notebook | |
|
239 | elif model['type'] == 'file': | |
|
240 | untitled = self.untitled_file | |
|
241 | else: | |
|
242 | raise HTTPError(400, "Unexpected model type: %r" % model['type']) | |
|
243 | model['name'] = self.increment_filename(untitled + ext, path) | |
|
244 | ||
|
245 | model['path'] = path | |
|
246 | model = self.save(model, model['name'], model['path']) | |
|
247 | return model | |
|
248 | ||
|
249 | def copy(self, from_name, to_name=None, path=''): | |
|
250 | """Copy an existing file and return its new model. | |
|
251 | ||
|
252 | If to_name not specified, increment `from_name-Copy#.ext`. | |
|
253 | ||
|
254 | copy_from can be a full path to a file, | |
|
255 | or just a base name. If a base name, `path` is used. | |
|
256 | """ | |
|
257 | path = path.strip('/') | |
|
258 | if '/' in from_name: | |
|
259 | from_path, from_name = from_name.rsplit('/', 1) | |
|
260 | else: | |
|
261 | from_path = path | |
|
262 | model = self.get_model(from_name, from_path) | |
|
263 | if model['type'] == 'directory': | |
|
264 | raise HTTPError(400, "Can't copy directories") | |
|
265 | if not to_name: | |
|
266 | base, ext = os.path.splitext(from_name) | |
|
267 | copy_name = u'{0}-Copy{1}'.format(base, ext) | |
|
268 | to_name = self.increment_filename(copy_name, path) | |
|
269 | model['name'] = to_name | |
|
270 | model['path'] = path | |
|
271 | model = self.save(model, to_name, path) | |
|
272 | return model | |
|
273 | ||
|
274 | def log_info(self): | |
|
275 | self.log.info(self.info_string()) | |
|
276 | ||
|
277 | def trust_notebook(self, name, path=''): | |
|
278 | """Explicitly trust a notebook | |
|
279 | ||
|
280 | Parameters | |
|
281 | ---------- | |
|
282 | name : string | |
|
283 | The filename of the notebook | |
|
284 | path : string | |
|
285 | The notebook's directory | |
|
286 | """ | |
|
287 | model = self.get_model(name, path) | |
|
288 | nb = model['content'] | |
|
289 | self.log.warn("Trusting notebook %s/%s", path, name) | |
|
290 | self.notary.mark_cells(nb, True) | |
|
291 | self.save(model, name, path) | |
|
292 | ||
|
293 | def check_and_sign(self, nb, name='', path=''): | |
|
294 | """Check for trusted cells, and sign the notebook. | |
|
295 | ||
|
296 | Called as a part of saving notebooks. | |
|
297 | ||
|
298 | Parameters | |
|
299 | ---------- | |
|
300 | nb : dict | |
|
301 | The notebook object (in nbformat.current format) | |
|
302 | name : string | |
|
303 | The filename of the notebook (for logging) | |
|
304 | path : string | |
|
305 | The notebook's directory (for logging) | |
|
306 | """ | |
|
307 | if self.notary.check_cells(nb): | |
|
308 | self.notary.sign(nb) | |
|
309 | else: | |
|
310 | self.log.warn("Saving untrusted notebook %s/%s", path, name) | |
|
311 | ||
|
312 | def mark_trusted_cells(self, nb, name='', path=''): | |
|
313 | """Mark cells as trusted if the notebook signature matches. | |
|
314 | ||
|
315 | Called as a part of loading notebooks. | |
|
316 | ||
|
317 | Parameters | |
|
318 | ---------- | |
|
319 | nb : dict | |
|
320 | The notebook object (in nbformat.current format) | |
|
321 | name : string | |
|
322 | The filename of the notebook (for logging) | |
|
323 | path : string | |
|
324 | The notebook's directory (for logging) | |
|
325 | """ | |
|
326 | trusted = self.notary.check_signature(nb) | |
|
327 | if not trusted: | |
|
328 | self.log.warn("Notebook %s/%s is not trusted", path, name) | |
|
329 | self.notary.mark_cells(nb, trusted) | |
|
330 | ||
|
331 | def should_list(self, name): | |
|
332 | """Should this file/directory name be displayed in a listing?""" | |
|
333 | return not any(fnmatch(name, glob) for glob in self.hide_globs) |
@@ -0,0 +1,91 b'' | |||
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | ], function(IPython, $, utils) { | |
|
9 | "use strict"; | |
|
10 | ||
|
11 | var KernelSelector = function(selector, notebook) { | |
|
12 | this.selector = selector; | |
|
13 | this.notebook = notebook; | |
|
14 | this.events = notebook.events; | |
|
15 | this.current_selection = notebook.default_kernel_name; | |
|
16 | this.kernelspecs = {}; | |
|
17 | if (this.selector !== undefined) { | |
|
18 | this.element = $(selector); | |
|
19 | this.request_kernelspecs(); | |
|
20 | } | |
|
21 | this.bind_events(); | |
|
22 | // Make the object globally available for user convenience & inspection | |
|
23 | IPython.kernelselector = this; | |
|
24 | }; | |
|
25 | ||
|
26 | KernelSelector.prototype.request_kernelspecs = function() { | |
|
27 | var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs'); | |
|
28 | $.ajax(url, {success: $.proxy(this._got_kernelspecs, this)}); | |
|
29 | }; | |
|
30 | ||
|
31 | KernelSelector.prototype._got_kernelspecs = function(data, status, xhr) { | |
|
32 | this.kernelspecs = {}; | |
|
33 | var menu = this.element.find("#kernel_selector"); | |
|
34 | var change_kernel_submenu = $("#menu-change-kernel-submenu"); | |
|
35 | for (var i = 0; i < data.length; i++) { | |
|
36 | var ks = data[i]; | |
|
37 | this.kernelspecs[ks.name] = ks; | |
|
38 | var ksentry = $("<li>").attr("id", "kernel-" +ks.name).append($('<a>') | |
|
39 | .attr('href', '#') | |
|
40 | .click($.proxy(this.change_kernel, this, ks.name)) | |
|
41 | .text(ks.display_name)); | |
|
42 | menu.append(ksentry); | |
|
43 | ||
|
44 | var ks_submenu_entry = $("<li>").attr("id", "kernel-submenu-"+ks.name).append($('<a>') | |
|
45 | .attr('href', '#') | |
|
46 | .click($.proxy(this.change_kernel, this, ks.name)) | |
|
47 | .text(ks.display_name)); | |
|
48 | change_kernel_submenu.append(ks_submenu_entry); | |
|
49 | } | |
|
50 | }; | |
|
51 | ||
|
52 | KernelSelector.prototype.change_kernel = function(kernel_name) { | |
|
53 | if (kernel_name === this.current_selection) { | |
|
54 | return; | |
|
55 | } | |
|
56 | var ks = this.kernelspecs[kernel_name]; | |
|
57 | try { | |
|
58 | this.notebook.start_session(kernel_name); | |
|
59 | } catch (e) { | |
|
60 | if (e.name === 'SessionAlreadyStarting') { | |
|
61 | console.log("Cannot change kernel while waiting for pending session start."); | |
|
62 | } else { | |
|
63 | // unhandled error | |
|
64 | throw e; | |
|
65 | } | |
|
66 | // only trigger spec_changed if change was successful | |
|
67 | return; | |
|
68 | } | |
|
69 | this.events.trigger('spec_changed.Kernel', ks); | |
|
70 | }; | |
|
71 | ||
|
72 | KernelSelector.prototype.bind_events = function() { | |
|
73 | var that = this; | |
|
74 | this.events.on('spec_changed.Kernel', function(event, data) { | |
|
75 | that.current_selection = data.name; | |
|
76 | that.element.find("#current_kernel_spec").find('.kernel_name').text(data.display_name); | |
|
77 | }); | |
|
78 | ||
|
79 | this.events.on('started.Session', function(events, session) { | |
|
80 | if (session.kernel_name !== that.current_selection) { | |
|
81 | // If we created a 'python' session, we only know if it's Python | |
|
82 | // 3 or 2 on the server's reply, so we fire the event again to | |
|
83 | // set things up. | |
|
84 | var ks = that.kernelspecs[session.kernel_name]; | |
|
85 | that.events.trigger('spec_changed.Kernel', ks); | |
|
86 | } | |
|
87 | }); | |
|
88 | }; | |
|
89 | ||
|
90 | return {'KernelSelector': KernelSelector}; | |
|
91 | }); |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100755 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 120000 | |
The requested commit or file is too big and content was truncated. Show full diff |
@@ -7,6 +7,7 b' docs/source/api/generated' | |||
|
7 | 7 | docs/source/config/options |
|
8 | 8 | docs/gh-pages |
|
9 | 9 | IPython/html/notebook/static/mathjax |
|
10 | IPython/html/static/style/*.map | |
|
10 | 11 | *.py[co] |
|
11 | 12 | __pycache__ |
|
12 | 13 | *.egg-info |
@@ -13,8 +13,10 b' before_install:' | |||
|
13 | 13 | # Pierre Carrier's PPA for PhantomJS and CasperJS |
|
14 | 14 | - time sudo add-apt-repository -y ppa:pcarrier/ppa |
|
15 | 15 | - time sudo apt-get update |
|
16 |
- time sudo apt-get install pandoc casperjs |
|
|
17 | - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests mock pyzmq jsonschema jsonpointer | |
|
16 | - time sudo apt-get install pandoc casperjs libzmq3-dev | |
|
17 | # pin tornado < 4 for js tests while phantom is on super old webkit | |
|
18 | - if [[ $GROUP == 'js' ]]; then pip install 'tornado<4'; fi | |
|
19 | - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests mock pyzmq jsonschema jsonpointer mistune | |
|
18 | 20 | install: |
|
19 | 21 | - time python setup.py install -q |
|
20 | 22 | script: |
@@ -1,31 +1,11 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | """ | |
|
3 | A base class for objects that are configurable. | |
|
4 | ||
|
5 | Inheritance diagram: | |
|
6 | ||
|
7 | .. inheritance-diagram:: IPython.config.configurable | |
|
8 | :parts: 3 | |
|
2 | """A base class for objects that are configurable.""" | |
|
9 | 3 | |
|
10 | Authors: | |
|
4 | # Copyright (c) IPython Development Team. | |
|
5 | # Distributed under the terms of the Modified BSD License. | |
|
11 | 6 | |
|
12 | * Brian Granger | |
|
13 | * Fernando Perez | |
|
14 | * Min RK | |
|
15 | """ | |
|
16 | 7 | from __future__ import print_function |
|
17 | 8 | |
|
18 | #----------------------------------------------------------------------------- | |
|
19 | # Copyright (C) 2008-2011 The IPython Development Team | |
|
20 | # | |
|
21 | # Distributed under the terms of the BSD License. The full license is in | |
|
22 | # the file COPYING, distributed as part of this software. | |
|
23 | #----------------------------------------------------------------------------- | |
|
24 | ||
|
25 | #----------------------------------------------------------------------------- | |
|
26 | # Imports | |
|
27 | #----------------------------------------------------------------------------- | |
|
28 | ||
|
29 | 9 | import logging |
|
30 | 10 | from copy import deepcopy |
|
31 | 11 | |
@@ -375,16 +355,12 b' class LoggingConfigurable(Configurable):' | |||
|
375 | 355 | """A parent class for Configurables that log. |
|
376 | 356 | |
|
377 | 357 | Subclasses have a log trait, and the default behavior |
|
378 | is to get the logger from the currently running Application | |
|
379 | via Application.instance().log. | |
|
358 | is to get the logger from the currently running Application. | |
|
380 | 359 | """ |
|
381 | 360 | |
|
382 | 361 | log = Instance('logging.Logger') |
|
383 | 362 | def _log_default(self): |
|
384 |
from IPython. |
|
|
385 | if Application.initialized(): | |
|
386 | return Application.instance().log | |
|
387 | else: | |
|
388 | return logging.getLogger() | |
|
363 | from IPython.utils import log | |
|
364 | return log.get_logger() | |
|
389 | 365 | |
|
390 | 366 |
@@ -1,27 +1,8 b'' | |||
|
1 | """A simple configuration system. | |
|
1 | # encoding: utf-8 | |
|
2 | """A simple configuration system.""" | |
|
2 | 3 | |
|
3 | Inheritance diagram: | |
|
4 | ||
|
5 | .. inheritance-diagram:: IPython.config.loader | |
|
6 | :parts: 3 | |
|
7 | ||
|
8 | Authors | |
|
9 | ------- | |
|
10 | * Brian Granger | |
|
11 | * Fernando Perez | |
|
12 | * Min RK | |
|
13 | """ | |
|
14 | ||
|
15 | #----------------------------------------------------------------------------- | |
|
16 | # Copyright (C) 2008-2011 The IPython Development Team | |
|
17 | # | |
|
18 | # Distributed under the terms of the BSD License. The full license is in | |
|
19 | # the file COPYING, distributed as part of this software. | |
|
20 | #----------------------------------------------------------------------------- | |
|
21 | ||
|
22 | #----------------------------------------------------------------------------- | |
|
23 | # Imports | |
|
24 | #----------------------------------------------------------------------------- | |
|
4 | # Copyright (c) IPython Development Team. | |
|
5 | # Distributed under the terms of the Modified BSD License. | |
|
25 | 6 | |
|
26 | 7 | import argparse |
|
27 | 8 | import copy |
@@ -308,11 +289,8 b' class ConfigLoader(object):' | |||
|
308 | 289 | """ |
|
309 | 290 | |
|
310 | 291 | def _log_default(self): |
|
311 |
from IPython. |
|
|
312 | if Application.initialized(): | |
|
313 | return Application.instance().log | |
|
314 | else: | |
|
315 | return logging.getLogger() | |
|
292 | from IPython.utils.log import get_logger | |
|
293 | return get_logger() | |
|
316 | 294 | |
|
317 | 295 | def __init__(self, log=None): |
|
318 | 296 | """A base class for config loaders. |
@@ -165,8 +165,6 b' class IPythonConsoleApp(ConnectionFileMixin):' | |||
|
165 | 165 | if argv is None: |
|
166 | 166 | argv = sys.argv[1:] |
|
167 | 167 | self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags) |
|
168 | # kernel should inherit default config file from frontend | |
|
169 | self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name) | |
|
170 | 168 | |
|
171 | 169 | def init_connection_file(self): |
|
172 | 170 | """find the connection file, and load the info if found. |
@@ -289,6 +287,7 b' class IPythonConsoleApp(ConnectionFileMixin):' | |||
|
289 | 287 | try: |
|
290 | 288 | self.kernel_manager = self.kernel_manager_class( |
|
291 | 289 | ip=self.ip, |
|
290 | session=self.session, | |
|
292 | 291 | transport=self.transport, |
|
293 | 292 | shell_port=self.shell_port, |
|
294 | 293 | iopub_port=self.iopub_port, |
@@ -326,6 +325,7 b' class IPythonConsoleApp(ConnectionFileMixin):' | |||
|
326 | 325 | self.kernel_client = self.kernel_manager.client() |
|
327 | 326 | else: |
|
328 | 327 | self.kernel_client = self.kernel_client_class( |
|
328 | session=self.session, | |
|
329 | 329 | ip=self.ip, |
|
330 | 330 | transport=self.transport, |
|
331 | 331 | shell_port=self.shell_port, |
@@ -80,6 +80,7 b' from IPython.core.error import TryNext' | |||
|
80 | 80 | from IPython.core.inputsplitter import ESC_MAGIC |
|
81 | 81 | from IPython.utils import generics |
|
82 | 82 | from IPython.utils import io |
|
83 | from IPython.utils.decorators import undoc | |
|
83 | 84 | from IPython.utils.dir2 import dir2 |
|
84 | 85 | from IPython.utils.process import arg_split |
|
85 | 86 | from IPython.utils.py3compat import builtin_mod, string_types |
@@ -216,7 +217,7 b' def penalize_magics_key(word):' | |||
|
216 | 217 | return word |
|
217 | 218 | |
|
218 | 219 | |
|
219 | ||
|
220 | @undoc | |
|
220 | 221 | class Bunch(object): pass |
|
221 | 222 | |
|
222 | 223 | |
@@ -865,6 +866,7 b' class IPCompleter(Completer):' | |||
|
865 | 866 | return argMatches |
|
866 | 867 | |
|
867 | 868 | def dict_key_matches(self, text): |
|
869 | "Match string keys in a dictionary, after e.g. 'foo[' " | |
|
868 | 870 | def get_keys(obj): |
|
869 | 871 | # Only allow completion for known in-memory dict-like types |
|
870 | 872 | if isinstance(obj, dict) or\ |
@@ -1010,9 +1012,6 b' class IPCompleter(Completer):' | |||
|
1010 | 1012 | def complete(self, text=None, line_buffer=None, cursor_pos=None): |
|
1011 | 1013 | """Find completions for the given text and line context. |
|
1012 | 1014 | |
|
1013 | This is called successively with state == 0, 1, 2, ... until it | |
|
1014 | returns None. The completion should begin with 'text'. | |
|
1015 | ||
|
1016 | 1015 | Note that both the text and the line_buffer are optional, but at least |
|
1017 | 1016 | one of them must be given. |
|
1018 | 1017 |
@@ -26,7 +26,13 b' from IPython.core.formatters import _safe_get_formatter_method' | |||
|
26 | 26 | from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode, |
|
27 | 27 | unicode_type) |
|
28 | 28 | from IPython.testing.skipdoctest import skip_doctest |
|
29 | from .displaypub import publish_display_data | |
|
29 | ||
|
30 | __all__ = ['display', 'display_pretty', 'display_html', 'display_markdown', | |
|
31 | 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json', | |
|
32 | 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject', | |
|
33 | 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'JSON', 'Javascript', | |
|
34 | 'Image', 'clear_output', 'set_matplotlib_formats', 'set_matplotlib_close', | |
|
35 | 'publish_display_data'] | |
|
30 | 36 | |
|
31 | 37 | #----------------------------------------------------------------------------- |
|
32 | 38 | # utility functions |
@@ -78,6 +84,48 b' def _display_mimetype(mimetype, objs, raw=False, metadata=None):' | |||
|
78 | 84 | # Main functions |
|
79 | 85 | #----------------------------------------------------------------------------- |
|
80 | 86 | |
|
87 | def publish_display_data(data, metadata=None, source=None): | |
|
88 | """Publish data and metadata to all frontends. | |
|
89 | ||
|
90 | See the ``display_data`` message in the messaging documentation for | |
|
91 | more details about this message type. | |
|
92 | ||
|
93 | The following MIME types are currently implemented: | |
|
94 | ||
|
95 | * text/plain | |
|
96 | * text/html | |
|
97 | * text/markdown | |
|
98 | * text/latex | |
|
99 | * application/json | |
|
100 | * application/javascript | |
|
101 | * image/png | |
|
102 | * image/jpeg | |
|
103 | * image/svg+xml | |
|
104 | ||
|
105 | Parameters | |
|
106 | ---------- | |
|
107 | data : dict | |
|
108 | A dictionary having keys that are valid MIME types (like | |
|
109 | 'text/plain' or 'image/svg+xml') and values that are the data for | |
|
110 | that MIME type. The data itself must be a JSON'able data | |
|
111 | structure. Minimally all data should have the 'text/plain' data, | |
|
112 | which can be displayed by all frontends. If more than the plain | |
|
113 | text is given, it is up to the frontend to decide which | |
|
114 | representation to use. | |
|
115 | metadata : dict | |
|
116 | A dictionary for metadata related to the data. This can contain | |
|
117 | arbitrary key, value pairs that frontends can use to interpret | |
|
118 | the data. mime-type keys matching those in data can be used | |
|
119 | to specify metadata about particular representations. | |
|
120 | source : str, deprecated | |
|
121 | Unused. | |
|
122 | """ | |
|
123 | from IPython.core.interactiveshell import InteractiveShell | |
|
124 | InteractiveShell.instance().display_pub.publish( | |
|
125 | data=data, | |
|
126 | metadata=metadata, | |
|
127 | ) | |
|
128 | ||
|
81 | 129 | def display(*objs, **kwargs): |
|
82 | 130 | """Display a Python object in all frontends. |
|
83 | 131 |
@@ -19,9 +19,11 b' from __future__ import print_function' | |||
|
19 | 19 | |
|
20 | 20 | from IPython.config.configurable import Configurable |
|
21 | 21 | from IPython.utils import io |
|
22 | from IPython.utils.py3compat import string_types | |
|
23 | 22 | from IPython.utils.traitlets import List |
|
24 | 23 | |
|
24 | # This used to be defined here - it is imported for backwards compatibility | |
|
25 | from .display import publish_display_data | |
|
26 | ||
|
25 | 27 | #----------------------------------------------------------------------------- |
|
26 | 28 | # Main payload class |
|
27 | 29 | #----------------------------------------------------------------------------- |
@@ -112,48 +114,3 b' class CapturingDisplayPublisher(DisplayPublisher):' | |||
|
112 | 114 | |
|
113 | 115 | # empty the list, *do not* reassign a new list |
|
114 | 116 | del self.outputs[:] |
|
115 | ||
|
116 | ||
|
117 | def publish_display_data(data, metadata=None, source=None): | |
|
118 | """Publish data and metadata to all frontends. | |
|
119 | ||
|
120 | See the ``display_data`` message in the messaging documentation for | |
|
121 | more details about this message type. | |
|
122 | ||
|
123 | The following MIME types are currently implemented: | |
|
124 | ||
|
125 | * text/plain | |
|
126 | * text/html | |
|
127 | * text/markdown | |
|
128 | * text/latex | |
|
129 | * application/json | |
|
130 | * application/javascript | |
|
131 | * image/png | |
|
132 | * image/jpeg | |
|
133 | * image/svg+xml | |
|
134 | ||
|
135 | Parameters | |
|
136 | ---------- | |
|
137 | data : dict | |
|
138 | A dictionary having keys that are valid MIME types (like | |
|
139 | 'text/plain' or 'image/svg+xml') and values that are the data for | |
|
140 | that MIME type. The data itself must be a JSON'able data | |
|
141 | structure. Minimally all data should have the 'text/plain' data, | |
|
142 | which can be displayed by all frontends. If more than the plain | |
|
143 | text is given, it is up to the frontend to decide which | |
|
144 | representation to use. | |
|
145 | metadata : dict | |
|
146 | A dictionary for metadata related to the data. This can contain | |
|
147 | arbitrary key, value pairs that frontends can use to interpret | |
|
148 | the data. mime-type keys matching those in data can be used | |
|
149 | to specify metadata about particular representations. | |
|
150 | source : str, deprecated | |
|
151 | Unused. | |
|
152 | """ | |
|
153 | from IPython.core.interactiveshell import InteractiveShell | |
|
154 | InteractiveShell.instance().display_pub.publish( | |
|
155 | data=data, | |
|
156 | metadata=metadata, | |
|
157 | ) | |
|
158 | ||
|
159 |
@@ -736,12 +736,13 b' class InteractiveShell(SingletonConfigurable):' | |||
|
736 | 736 | # stdlib venv may symlink sys.executable, so we can't use realpath. |
|
737 | 737 | # but others can symlink *to* the venv Python, so we can't just use sys.executable. |
|
738 | 738 | # So we just check every item in the symlink tree (generally <= 3) |
|
739 | p = sys.executable | |
|
739 | p = os.path.normcase(sys.executable) | |
|
740 | 740 | paths = [p] |
|
741 | 741 | while os.path.islink(p): |
|
742 | p = os.path.join(os.path.dirname(p), os.readlink(p)) | |
|
742 | p = os.path.normcase(os.path.join(os.path.dirname(p), os.readlink(p))) | |
|
743 | 743 | paths.append(p) |
|
744 | if any(p.startswith(os.environ['VIRTUAL_ENV']) for p in paths): | |
|
744 | p_venv = os.path.normcase(os.environ['VIRTUAL_ENV']) | |
|
745 | if any(p.startswith(p_venv) for p in paths): | |
|
745 | 746 | # Running properly in the virtualenv, don't need to do anything |
|
746 | 747 | return |
|
747 | 748 | |
@@ -910,7 +911,8 b' class InteractiveShell(SingletonConfigurable):' | |||
|
910 | 911 | try: |
|
911 | 912 | main_mod = self._main_mod_cache[filename] |
|
912 | 913 | except KeyError: |
|
913 |
main_mod = self._main_mod_cache[filename] = types.ModuleType( |
|
|
914 | main_mod = self._main_mod_cache[filename] = types.ModuleType( | |
|
915 | py3compat.cast_bytes_py2(modname), | |
|
914 | 916 | doc="Module created for script run in IPython") |
|
915 | 917 | else: |
|
916 | 918 | main_mod.__dict__.clear() |
@@ -2918,7 +2920,6 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2918 | 2920 | False : successful execution. |
|
2919 | 2921 | True : an error occurred. |
|
2920 | 2922 | """ |
|
2921 | ||
|
2922 | 2923 | # Set our own excepthook in case the user code tries to call it |
|
2923 | 2924 | # directly, so that the IPython crash handler doesn't get triggered |
|
2924 | 2925 | old_excepthook,sys.excepthook = sys.excepthook, self.excepthook |
@@ -3087,6 +3088,7 b' class InteractiveShell(SingletonConfigurable):' | |||
|
3087 | 3088 | self.tempdirs.append(dirname) |
|
3088 | 3089 | |
|
3089 | 3090 | handle, filename = tempfile.mkstemp('.py', prefix, dir=dirname) |
|
3091 | os.close(handle) # On Windows, there can only be one open handle on a file | |
|
3090 | 3092 | self.tempfiles.append(filename) |
|
3091 | 3093 | |
|
3092 | 3094 | if data: |
@@ -193,6 +193,8 b' class ScriptMagics(Magics):' | |||
|
193 | 193 | else: |
|
194 | 194 | raise |
|
195 | 195 | |
|
196 | if not cell.endswith('\n'): | |
|
197 | cell += '\n' | |
|
196 | 198 | cell = cell.encode('utf8', 'replace') |
|
197 | 199 | if args.bg: |
|
198 | 200 | self.bg_processes.append(p) |
@@ -6,6 +6,7 b'' | |||
|
6 | 6 | #----------------------------------------------------------------------------- |
|
7 | 7 | |
|
8 | 8 | # stdlib |
|
9 | import io | |
|
9 | 10 | import os |
|
10 | 11 | import sys |
|
11 | 12 | import tempfile |
@@ -124,7 +125,7 b' def test_history():' | |||
|
124 | 125 | # Cross testing: check that magic %save can get previous session. |
|
125 | 126 | testfilename = os.path.realpath(os.path.join(tmpdir, "test.py")) |
|
126 | 127 | ip.magic("save " + testfilename + " ~1/1-3") |
|
127 |
with |
|
|
128 | with io.open(testfilename, encoding='utf-8') as testfile: | |
|
128 | 129 | nt.assert_equal(testfile.read(), |
|
129 | 130 | u"# coding: utf-8\n" + u"\n".join(hist)+u"\n") |
|
130 | 131 |
@@ -462,6 +462,21 b' class InteractiveShellTestCase(unittest.TestCase):' | |||
|
462 | 462 | ip.run_cell("d = 1/2", shell_futures=True) |
|
463 | 463 | self.assertEqual(ip.user_ns['d'], 0) |
|
464 | 464 | |
|
465 | def test_mktempfile(self): | |
|
466 | filename = ip.mktempfile() | |
|
467 | # Check that we can open the file again on Windows | |
|
468 | with open(filename, 'w') as f: | |
|
469 | f.write('abc') | |
|
470 | ||
|
471 | filename = ip.mktempfile(data='blah') | |
|
472 | with open(filename, 'r') as f: | |
|
473 | self.assertEqual(f.read(), 'blah') | |
|
474 | ||
|
475 | def test_new_main_mod(self): | |
|
476 | # Smoketest to check that this accepts a unicode module name | |
|
477 | name = u'jiefmw' | |
|
478 | mod = ip.new_main_mod(u'%s.py' % name, name) | |
|
479 | self.assertEqual(mod.__name__, name) | |
|
465 | 480 | |
|
466 | 481 | class TestSafeExecfileNonAsciiPath(unittest.TestCase): |
|
467 | 482 |
@@ -9,6 +9,7 b' from IPython.testing import tools as tt' | |||
|
9 | 9 | from IPython.testing.decorators import onlyif_unicode_paths |
|
10 | 10 | from IPython.utils.syspathcontext import prepended_to_syspath |
|
11 | 11 | from IPython.utils.tempdir import TemporaryDirectory |
|
12 | from IPython.utils.py3compat import PY3 | |
|
12 | 13 | |
|
13 | 14 | ip = get_ipython() |
|
14 | 15 | |
@@ -147,3 +148,37 b' class SyntaxErrorTest(unittest.TestCase):' | |||
|
147 | 148 | except ValueError: |
|
148 | 149 | with tt.AssertPrints('QWERTY'): |
|
149 | 150 | ip.showsyntaxerror() |
|
151 | ||
|
152 | ||
|
153 | class Python3ChainedExceptionsTest(unittest.TestCase): | |
|
154 | DIRECT_CAUSE_ERROR_CODE = """ | |
|
155 | try: | |
|
156 | x = 1 + 2 | |
|
157 | print(not_defined_here) | |
|
158 | except Exception as e: | |
|
159 | x += 55 | |
|
160 | x - 1 | |
|
161 | y = {} | |
|
162 | raise KeyError('uh') from e | |
|
163 | """ | |
|
164 | ||
|
165 | EXCEPTION_DURING_HANDLING_CODE = """ | |
|
166 | try: | |
|
167 | x = 1 + 2 | |
|
168 | print(not_defined_here) | |
|
169 | except Exception as e: | |
|
170 | x += 55 | |
|
171 | x - 1 | |
|
172 | y = {} | |
|
173 | raise KeyError('uh') | |
|
174 | """ | |
|
175 | ||
|
176 | def test_direct_cause_error(self): | |
|
177 | if PY3: | |
|
178 | with tt.AssertPrints(["KeyError", "NameError", "direct cause"]): | |
|
179 | ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE) | |
|
180 | ||
|
181 | def test_exception_during_handling_error(self): | |
|
182 | if PY3: | |
|
183 | with tt.AssertPrints(["KeyError", "NameError", "During handling"]): | |
|
184 | ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE) |
@@ -39,7 +39,7 b" Give it a shot--you'll love it or you'll hate it." | |||
|
39 | 39 | Verbose). |
|
40 | 40 | |
|
41 | 41 | |
|
42 |
Installation instructions for |
|
|
42 | Installation instructions for VerboseTB:: | |
|
43 | 43 | |
|
44 | 44 | import sys,ultratb |
|
45 | 45 | sys.excepthook = ultratb.VerboseTB() |
@@ -141,6 +141,7 b' def inspect_error():' | |||
|
141 | 141 | error('Internal Python error in the inspect module.\n' |
|
142 | 142 | 'Below is the traceback from this internal error.\n') |
|
143 | 143 | |
|
144 | ||
|
144 | 145 | # This function is a monkeypatch we apply to the Python inspect module. We have |
|
145 | 146 | # now found when it's needed (see discussion on issue gh-1456), and we have a |
|
146 | 147 | # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if |
@@ -220,9 +221,11 b' def findsource(object):' | |||
|
220 | 221 | return lines, lnum |
|
221 | 222 | raise IOError('could not find code object') |
|
222 | 223 | |
|
224 | ||
|
223 | 225 | # Monkeypatch inspect to apply our bugfix. |
|
224 | 226 | def with_patch_inspect(f): |
|
225 | 227 | """decorator for monkeypatching inspect.findsource""" |
|
228 | ||
|
226 | 229 | def wrapped(*args, **kwargs): |
|
227 | 230 | save_findsource = inspect.findsource |
|
228 | 231 | inspect.findsource = findsource |
@@ -230,8 +233,10 b' def with_patch_inspect(f):' | |||
|
230 | 233 | return f(*args, **kwargs) |
|
231 | 234 | finally: |
|
232 | 235 | inspect.findsource = save_findsource |
|
236 | ||
|
233 | 237 | return wrapped |
|
234 | 238 | |
|
239 | ||
|
235 | 240 | def fix_frame_records_filenames(records): |
|
236 | 241 | """Try to fix the filenames in each record from inspect.getinnerframes(). |
|
237 | 242 | |
@@ -257,7 +262,6 b' def _fixed_getinnerframes(etb, context=1,tb_offset=0):' | |||
|
257 | 262 |
LNUM_POS, LINES_POS, INDEX_POS = |
|
258 | 263 | |
|
259 | 264 |
records |
|
260 | ||
|
261 | 265 | # If the error is at the console, don't build any context, since it would |
|
262 | 266 | # otherwise produce 5 blank lines printed out (there is no file at the |
|
263 | 267 | # console) |
@@ -290,6 +294,7 b' def _fixed_getinnerframes(etb, context=1,tb_offset=0):' | |||
|
290 | 294 | |
|
291 | 295 | _parser = PyColorize.Parser() |
|
292 | 296 | |
|
297 | ||
|
293 | 298 | def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None): |
|
294 | 299 | numbers_width = INDENT_SIZE - 1 |
|
295 | 300 | res = [] |
@@ -510,7 +515,7 b' class ListTB(TBTools):' | |||
|
510 | 515 | ## out_list.append(lines[-1]) |
|
511 | 516 | |
|
512 | 517 | # This means it was indenting everything but the last line by a little |
|
513 | # bit. I've disabled this for now, but if we see ugliness somewhre we | |
|
518 | # bit. I've disabled this for now, but if we see ugliness somewhere we | |
|
514 | 519 | # can restore it. |
|
515 | 520 | |
|
516 | 521 | return out_list |
@@ -550,7 +555,6 b' class ListTB(TBTools):' | |||
|
550 | 555 | item += '%s %s%s\n' % (Colors.line, line.strip(), |
|
551 | 556 |
|
|
552 | 557 | list.append(item) |
|
553 | #from pprint import pformat; print 'LISTTB', pformat(list) # dbg | |
|
554 | 558 | return list |
|
555 | 559 | |
|
556 | 560 | def _format_exception_only(self, etype, value): |
@@ -576,7 +580,6 b' class ListTB(TBTools):' | |||
|
576 | 580 | else: |
|
577 | 581 | if issubclass(etype, SyntaxError): |
|
578 | 582 | have_filedata = True |
|
579 | #print 'filename is',filename # dbg | |
|
580 | 583 | if not value.filename: value.filename = "<string>" |
|
581 | 584 | if value.lineno: |
|
582 | 585 | lineno = value.lineno |
@@ -636,7 +639,6 b' class ListTB(TBTools):' | |||
|
636 | 639 | """ |
|
637 | 640 | return ListTB.structured_traceback(self, etype, value, []) |
|
638 | 641 | |
|
639 | ||
|
640 | 642 | def show_exception_only(self, etype, evalue): |
|
641 | 643 | """Only print the exception type and message, without a traceback. |
|
642 | 644 | |
@@ -659,6 +661,7 b' class ListTB(TBTools):' | |||
|
659 | 661 | except: |
|
660 | 662 | return '<unprintable %s object>' % type(value).__name__ |
|
661 | 663 | |
|
664 | ||
|
662 | 665 | #---------------------------------------------------------------------------- |
|
663 | 666 | class VerboseTB(TBTools): |
|
664 | 667 | """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead |
@@ -691,103 +694,14 b' class VerboseTB(TBTools):' | |||
|
691 | 694 | check_cache = linecache.checkcache |
|
692 | 695 | self.check_cache = check_cache |
|
693 | 696 | |
|
694 | def structured_traceback(self, etype, evalue, etb, tb_offset=None, | |
|
695 | context=5): | |
|
696 | """Return a nice text document describing the traceback.""" | |
|
697 | ||
|
698 | tb_offset = self.tb_offset if tb_offset is None else tb_offset | |
|
699 | ||
|
700 | # some locals | |
|
701 | try: | |
|
702 | etype = etype.__name__ | |
|
703 | except AttributeError: | |
|
704 | pass | |
|
697 | def format_records(self, records): | |
|
705 | 698 |
Colors |
|
706 | 699 |
ColorsNormal |
|
707 | 700 |
col_scheme |
|
708 | 701 |
indent |
|
709 | 702 |
em_normal |
|
710 | 703 |
undefined |
|
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 | 704 | frames = [] |
|
766 | # Flush cache before calling inspect. This helps alleviate some of the | |
|
767 | # problems with python 2.3's inspect.py. | |
|
768 | ##self.check_cache() | |
|
769 | # Drop topmost frames if requested | |
|
770 | try: | |
|
771 | # Try the default getinnerframes and Alex's: Alex's fixes some | |
|
772 | # problems, but it generates empty tracebacks for console errors | |
|
773 | # (5 blanks lines) where none should be returned. | |
|
774 | #records = inspect.getinnerframes(etb, context)[tb_offset:] | |
|
775 | #print 'python records:', records # dbg | |
|
776 | records = _fixed_getinnerframes(etb, context, tb_offset) | |
|
777 | #print 'alex records:', records # dbg | |
|
778 | except: | |
|
779 | ||
|
780 | # FIXME: I've been getting many crash reports from python 2.3 | |
|
781 | # users, traceable to inspect.py. If I can find a small test-case | |
|
782 | # to reproduce this, I should either write a better workaround or | |
|
783 | # file a bug report against inspect (if that's the real problem). | |
|
784 | # So far, I haven't been able to find an isolated example to | |
|
785 | # reproduce the problem. | |
|
786 | inspect_error() | |
|
787 | traceback.print_exc(file=self.ostream) | |
|
788 | info('\nUnfortunately, your original traceback can not be constructed.\n') | |
|
789 | return '' | |
|
790 | ||
|
791 | 705 | # build some color string templates outside these nested loops |
|
792 | 706 |
tpl_link |
|
793 | 707 |
tpl_call |
@@ -798,11 +712,11 b' class VerboseTB(TBTools):' | |||
|
798 | 712 | tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal, |
|
799 | 713 | Colors.vName, ColorsNormal) |
|
800 | 714 |
tpl_name_val |
|
715 | ||
|
801 | 716 |
tpl_line |
|
802 | 717 |
tpl_line_em |
|
803 | 718 |
|
|
804 | 719 | |
|
805 | # now, loop over all records printing context and info | |
|
806 | 720 | abspath = os.path.abspath |
|
807 | 721 | for frame, file, lnum, func, lines, index in records: |
|
808 | 722 | #print '*** record:',file,lnum,func,lines,index # dbg |
@@ -891,8 +805,10 b' class VerboseTB(TBTools):' | |||
|
891 | 805 | elif token_type == tokenize.NEWLINE: |
|
892 | 806 | break |
|
893 | 807 | |
|
894 | except (IndexError, UnicodeDecodeError): | |
|
808 | except (IndexError, UnicodeDecodeError, SyntaxError): | |
|
895 | 809 | # signals exit of tokenizer |
|
810 | # SyntaxError can occur if the file is not actually Python | |
|
811 | # - see gh-6300 | |
|
896 | 812 | pass |
|
897 | 813 | except tokenize.TokenError as msg: |
|
898 | 814 | _m = ("An unexpected error occurred while tokenizing input\n" |
@@ -943,6 +859,43 b' class VerboseTB(TBTools):' | |||
|
943 | 859 | _format_traceback_lines(lnum,index,lines,Colors,lvals, |
|
944 | 860 | col_scheme)))) |
|
945 | 861 | |
|
862 | return frames | |
|
863 | ||
|
864 | def prepare_chained_exception_message(self, cause): | |
|
865 | direct_cause = "\nThe above exception was the direct cause of the following exception:\n" | |
|
866 | exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n" | |
|
867 | ||
|
868 | if cause: | |
|
869 | message = [[direct_cause]] | |
|
870 | else: | |
|
871 | message = [[exception_during_handling]] | |
|
872 | return message | |
|
873 | ||
|
874 | def prepare_header(self, etype, long_version=False): | |
|
875 | colors = self.Colors # just a shorthand + quicker name lookup | |
|
876 | colorsnormal = colors.Normal # used a lot | |
|
877 | exc = '%s%s%s' % (colors.excName, etype, colorsnormal) | |
|
878 | if long_version: | |
|
879 | # Header with the exception type, python version, and date | |
|
880 | pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable | |
|
881 | date = time.ctime(time.time()) | |
|
882 | ||
|
883 | head = '%s%s%s\n%s%s%s\n%s' % (colors.topline, '-' * 75, colorsnormal, | |
|
884 | exc, ' ' * (75 - len(str(etype)) - len(pyver)), | |
|
885 | pyver, date.rjust(75) ) | |
|
886 | head += "\nA problem occurred executing Python code. Here is the sequence of function" \ | |
|
887 | "\ncalls leading up to the error, with the most recent (innermost) call last." | |
|
888 | else: | |
|
889 | # Simplified header | |
|
890 | head = '%s%s' % (exc, 'Traceback (most recent call last)'. \ | |
|
891 | rjust(75 - len(str(etype))) ) | |
|
892 | ||
|
893 | return head | |
|
894 | ||
|
895 | def format_exception(self, etype, evalue): | |
|
896 | colors = self.Colors # just a shorthand + quicker name lookup | |
|
897 | colorsnormal = colors.Normal # used a lot | |
|
898 | indent = ' ' * INDENT_SIZE | |
|
946 | 899 | # Get (safely) a string form of the exception info |
|
947 | 900 | try: |
|
948 | 901 | etype_str,evalue_str = map(str,(etype,evalue)) |
@@ -951,38 +904,123 b' class VerboseTB(TBTools):' | |||
|
951 | 904 | etype,evalue = str,sys.exc_info()[:2] |
|
952 | 905 | etype_str,evalue_str = map(str,(etype,evalue)) |
|
953 | 906 | # ... and format it |
|
954 |
exception = ['%s%s%s: %s' % ( |
|
|
955 |
|
|
|
907 | exception = ['%s%s%s: %s' % (colors.excName, etype_str, | |
|
908 | colorsnormal, py3compat.cast_unicode(evalue_str))] | |
|
909 | ||
|
956 | 910 | if (not py3compat.PY3) and type(evalue) is types.InstanceType: |
|
957 | 911 | try: |
|
958 | 912 | names = [w for w in dir(evalue) if isinstance(w, py3compat.string_types)] |
|
959 | 913 | except: |
|
960 | # Every now and then, an object with funny inernals blows up | |
|
914 | # Every now and then, an object with funny internals blows up | |
|
961 | 915 | # when dir() is called on it. We do the best we can to report |
|
962 | 916 | # the problem and continue |
|
963 | 917 | _m = '%sException reporting error (object with broken dir())%s:' |
|
964 |
exception.append(_m % ( |
|
|
918 | exception.append(_m % (colors.excName, colorsnormal)) | |
|
965 | 919 | etype_str,evalue_str = map(str,sys.exc_info()[:2]) |
|
966 |
exception.append('%s%s%s: %s' % ( |
|
|
967 |
|
|
|
920 | exception.append('%s%s%s: %s' % (colors.excName, etype_str, | |
|
921 | colorsnormal, py3compat.cast_unicode(evalue_str))) | |
|
968 | 922 | names = [] |
|
969 | 923 | for name in names: |
|
970 | 924 | value = text_repr(getattr(evalue, name)) |
|
971 | 925 | exception.append('\n%s%s = %s' % (indent, name, value)) |
|
972 | 926 | |
|
973 | # vds: >> | |
|
927 | return exception | |
|
928 | ||
|
929 | def format_exception_as_a_whole(self, etype, evalue, etb, number_of_lines_of_context, tb_offset): | |
|
930 | # some locals | |
|
931 | try: | |
|
932 | etype = etype.__name__ | |
|
933 | except AttributeError: | |
|
934 | pass | |
|
935 | ||
|
936 | tb_offset = self.tb_offset if tb_offset is None else tb_offset | |
|
937 | head = self.prepare_header(etype, self.long_header) | |
|
938 | records = self.get_records(etb, number_of_lines_of_context, tb_offset) | |
|
939 | ||
|
940 | frames = self.format_records(records) | |
|
941 | if records is None: | |
|
942 | return "" | |
|
943 | ||
|
944 | formatted_exception = self.format_exception(etype, evalue) | |
|
974 | 945 | if records: |
|
975 | 946 |
|
|
976 | #print "file:", str(file), "linenb", str(lnum) # dbg | |
|
977 | 947 |
|
|
978 | 948 |
|
|
979 | 949 |
|
|
980 | 950 |
|
|
981 | # vds: << | |
|
982 | 951 | |
|
983 | # return all our info assembled as a single string | |
|
984 | # return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) ) | |
|
985 | return [head] + frames + [''.join(exception[0])] | |
|
952 | return [[head] + frames + [''.join(formatted_exception[0])]] | |
|
953 | ||
|
954 | def get_records(self, etb, number_of_lines_of_context, tb_offset): | |
|
955 | try: | |
|
956 | # Try the default getinnerframes and Alex's: Alex's fixes some | |
|
957 | # problems, but it generates empty tracebacks for console errors | |
|
958 | # (5 blanks lines) where none should be returned. | |
|
959 | return _fixed_getinnerframes(etb, number_of_lines_of_context, tb_offset) | |
|
960 | except: | |
|
961 | # FIXME: I've been getting many crash reports from python 2.3 | |
|
962 | # users, traceable to inspect.py. If I can find a small test-case | |
|
963 | # to reproduce this, I should either write a better workaround or | |
|
964 | # file a bug report against inspect (if that's the real problem). | |
|
965 | # So far, I haven't been able to find an isolated example to | |
|
966 | # reproduce the problem. | |
|
967 | inspect_error() | |
|
968 | traceback.print_exc(file=self.ostream) | |
|
969 | info('\nUnfortunately, your original traceback can not be constructed.\n') | |
|
970 | return None | |
|
971 | ||
|
972 | def get_parts_of_chained_exception(self, evalue): | |
|
973 | def get_chained_exception(exception_value): | |
|
974 | cause = getattr(exception_value, '__cause__', None) | |
|
975 | if cause: | |
|
976 | return cause | |
|
977 | return getattr(exception_value, '__context__', None) | |
|
978 | ||
|
979 | chained_evalue = get_chained_exception(evalue) | |
|
980 | ||
|
981 | if chained_evalue: | |
|
982 | return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__ | |
|
983 | ||
|
984 | def structured_traceback(self, etype, evalue, etb, tb_offset=None, | |
|
985 | number_of_lines_of_context=5): | |
|
986 | """Return a nice text document describing the traceback.""" | |
|
987 | ||
|
988 | formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context, | |
|
989 | tb_offset) | |
|
990 | ||
|
991 | colors = self.Colors # just a shorthand + quicker name lookup | |
|
992 | colorsnormal = colors.Normal # used a lot | |
|
993 | head = '%s%s%s' % (colors.topline, '-' * 75, colorsnormal) | |
|
994 | structured_traceback_parts = [head] | |
|
995 | if py3compat.PY3: | |
|
996 | chained_exceptions_tb_offset = 0 | |
|
997 | lines_of_context = 3 | |
|
998 | formatted_exceptions = formatted_exception | |
|
999 | exception = self.get_parts_of_chained_exception(evalue) | |
|
1000 | if exception: | |
|
1001 | formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__) | |
|
1002 | etype, evalue, etb = exception | |
|
1003 | else: | |
|
1004 | evalue = None | |
|
1005 | while evalue: | |
|
1006 | formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context, | |
|
1007 | chained_exceptions_tb_offset) | |
|
1008 | exception = self.get_parts_of_chained_exception(evalue) | |
|
1009 | ||
|
1010 | if exception: | |
|
1011 | formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__) | |
|
1012 | etype, evalue, etb = exception | |
|
1013 | else: | |
|
1014 | evalue = None | |
|
1015 | ||
|
1016 | # we want to see exceptions in a reversed order: | |
|
1017 | # the first exception should be on top | |
|
1018 | for formatted_exception in reversed(formatted_exceptions): | |
|
1019 | structured_traceback_parts += formatted_exception | |
|
1020 | else: | |
|
1021 | structured_traceback_parts += formatted_exception[0] | |
|
1022 | ||
|
1023 | return structured_traceback_parts | |
|
986 | 1024 | |
|
987 | 1025 | def debugger(self,force=False): |
|
988 | 1026 | """Call up the pdb debugger if desired, always clean up the tb |
@@ -1050,6 +1088,7 b' class VerboseTB(TBTools):' | |||
|
1050 | 1088 | except KeyboardInterrupt: |
|
1051 | 1089 | print("\nKeyboardInterrupt") |
|
1052 | 1090 | |
|
1091 | ||
|
1053 | 1092 | #---------------------------------------------------------------------------- |
|
1054 | 1093 | class FormattedTB(VerboseTB, ListTB): |
|
1055 | 1094 | """Subclass ListTB but allow calling with a traceback. |
@@ -1089,13 +1128,13 b' class FormattedTB(VerboseTB, ListTB):' | |||
|
1089 | 1128 | else: |
|
1090 | 1129 | return None |
|
1091 | 1130 | |
|
1092 | def structured_traceback(self, etype, value, tb, tb_offset=None, context=5): | |
|
1131 | def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5): | |
|
1093 | 1132 | tb_offset = self.tb_offset if tb_offset is None else tb_offset |
|
1094 | 1133 | mode = self.mode |
|
1095 | 1134 | if mode in self.verbose_modes: |
|
1096 | 1135 | # Verbose modes need a full traceback |
|
1097 | 1136 | return VerboseTB.structured_traceback( |
|
1098 | self, etype, value, tb, tb_offset, context | |
|
1137 | self, etype, value, tb, tb_offset, number_of_lines_of_context | |
|
1099 | 1138 | ) |
|
1100 | 1139 | else: |
|
1101 | 1140 | # We must check the source cache because otherwise we can print |
@@ -1104,7 +1143,7 b' class FormattedTB(VerboseTB, ListTB):' | |||
|
1104 | 1143 | # Now we can extract and format the exception |
|
1105 | 1144 | elist = self._extract_tb(tb) |
|
1106 | 1145 | return ListTB.structured_traceback( |
|
1107 | self, etype, value, elist, tb_offset, context | |
|
1146 | self, etype, value, elist, tb_offset, number_of_lines_of_context | |
|
1108 | 1147 | ) |
|
1109 | 1148 | |
|
1110 | 1149 | def stb2text(self, stb): |
@@ -1131,7 +1170,7 b' class FormattedTB(VerboseTB, ListTB):' | |||
|
1131 | 1170 | # Set the join character for generating text tracebacks |
|
1132 | 1171 | self.tb_join_char = self._join_chars[self.mode] |
|
1133 | 1172 | |
|
1134 | # some convenient shorcuts | |
|
1173 | # some convenient shortcuts | |
|
1135 | 1174 | def plain(self): |
|
1136 | 1175 | self.set_mode(self.valid_modes[0]) |
|
1137 | 1176 | |
@@ -1141,6 +1180,7 b' class FormattedTB(VerboseTB, ListTB):' | |||
|
1141 | 1180 | def verbose(self): |
|
1142 | 1181 | self.set_mode(self.valid_modes[2]) |
|
1143 | 1182 | |
|
1183 | ||
|
1144 | 1184 | #---------------------------------------------------------------------------- |
|
1145 | 1185 | class AutoFormattedTB(FormattedTB): |
|
1146 | 1186 | """A traceback printer which can be called on the fly. |
@@ -1167,7 +1207,6 b' class AutoFormattedTB(FormattedTB):' | |||
|
1167 | 1207 | per-call basis (this overrides temporarily the instance's tb_offset |
|
1168 | 1208 | given at initialization time. """ |
|
1169 | 1209 | |
|
1170 | ||
|
1171 | 1210 | if out is None: |
|
1172 | 1211 | out = self.ostream |
|
1173 | 1212 | out.flush() |
@@ -1182,18 +1221,20 b' class AutoFormattedTB(FormattedTB):' | |||
|
1182 | 1221 | print("\nKeyboardInterrupt") |
|
1183 | 1222 | |
|
1184 | 1223 | def structured_traceback(self, etype=None, value=None, tb=None, |
|
1185 | tb_offset=None, context=5): | |
|
1224 | tb_offset=None, number_of_lines_of_context=5): | |
|
1186 | 1225 | if etype is None: |
|
1187 | 1226 | etype,value,tb = sys.exc_info() |
|
1188 | 1227 | self.tb = tb |
|
1189 | 1228 | return FormattedTB.structured_traceback( |
|
1190 | self, etype, value, tb, tb_offset, context) | |
|
1229 | self, etype, value, tb, tb_offset, number_of_lines_of_context) | |
|
1230 | ||
|
1191 | 1231 | |
|
1192 | 1232 | #--------------------------------------------------------------------------- |
|
1193 | 1233 | |
|
1194 | 1234 | # A simple class to preserve Nathan's original functionality. |
|
1195 | 1235 | class ColorTB(FormattedTB): |
|
1196 | 1236 | """Shorthand to initialize a FormattedTB in Linux colors mode.""" |
|
1237 | ||
|
1197 | 1238 | def __init__(self,color_scheme='Linux',call_pdb=0): |
|
1198 | 1239 | FormattedTB.__init__(self,color_scheme=color_scheme, |
|
1199 | 1240 | call_pdb=call_pdb) |
@@ -1208,6 +1249,7 b' class SyntaxTB(ListTB):' | |||
|
1208 | 1249 | |
|
1209 | 1250 | def __call__(self, etype, value, elist): |
|
1210 | 1251 | self.last_syntax_error = value |
|
1252 | ||
|
1211 | 1253 | ListTB.__call__(self,etype,value,elist) |
|
1212 | 1254 | |
|
1213 | 1255 | def structured_traceback(self, etype, value, elist, tb_offset=None, |
@@ -1236,7 +1278,46 b' class SyntaxTB(ListTB):' | |||
|
1236 | 1278 | return ''.join(stb) |
|
1237 | 1279 | |
|
1238 | 1280 | |
|
1281 | # some internal-use functions | |
|
1282 | def text_repr(value): | |
|
1283 | """Hopefully pretty robust repr equivalent.""" | |
|
1284 | # this is pretty horrible but should always return *something* | |
|
1285 | try: | |
|
1286 | return pydoc.text.repr(value) | |
|
1287 | except KeyboardInterrupt: | |
|
1288 | raise | |
|
1289 | except: | |
|
1290 | try: | |
|
1291 | return repr(value) | |
|
1292 | except KeyboardInterrupt: | |
|
1293 | raise | |
|
1294 | except: | |
|
1295 | try: | |
|
1296 | # all still in an except block so we catch | |
|
1297 | # getattr raising | |
|
1298 | name = getattr(value, '__name__', None) | |
|
1299 | if name: | |
|
1300 | # ick, recursion | |
|
1301 | return text_repr(name) | |
|
1302 | klass = getattr(value, '__class__', None) | |
|
1303 | if klass: | |
|
1304 | return '%s instance' % text_repr(klass) | |
|
1305 | except KeyboardInterrupt: | |
|
1306 | raise | |
|
1307 | except: | |
|
1308 | return 'UNRECOVERABLE REPR FAILURE' | |
|
1309 | ||
|
1310 | ||
|
1311 | def eqrepr(value, repr=text_repr): | |
|
1312 | return '=%s' % repr(value) | |
|
1313 | ||
|
1314 | ||
|
1315 | def nullrepr(value, repr=text_repr): | |
|
1316 | return '' | |
|
1317 | ||
|
1318 | ||
|
1239 | 1319 | #---------------------------------------------------------------------------- |
|
1320 | ||
|
1240 | 1321 | # module testing (minimal) |
|
1241 | 1322 | if __name__ == "__main__": |
|
1242 | 1323 | def spam(c, d_e): |
@@ -136,7 +136,7 b' def extract_zip(fd, dest):' | |||
|
136 | 136 | os.rename(os.path.join(parent, topdir), dest) |
|
137 | 137 | |
|
138 | 138 | |
|
139 |
def install_mathjax(tag=' |
|
|
139 | def install_mathjax(tag='2.4.0', dest=default_dest, replace=False, file=None, extractor=extract_tar): | |
|
140 | 140 | """Download and/or install MathJax for offline use. |
|
141 | 141 | |
|
142 | 142 | This will install mathjax to the nbextensions dir in your IPYTHONDIR. |
@@ -150,8 +150,8 b" def install_mathjax(tag='v2.2', dest=default_dest, replace=False, file=None, ext" | |||
|
150 | 150 | Whether to remove and replace an existing install. |
|
151 | 151 | dest : str [IPYTHONDIR/nbextensions/mathjax] |
|
152 | 152 | Where to install mathjax |
|
153 |
tag : str [' |
|
|
154 |
Which tag to download. Default is ' |
|
|
153 | tag : str ['2.4.0'] | |
|
154 | Which tag to download. Default is '2.4.0', the current stable release, | |
|
155 | 155 | but alternatives include 'v1.1a' and 'master'. |
|
156 | 156 | file : file like object [ defualt to content of https://github.com/mathjax/MathJax/tarball/#{tag}] |
|
157 | 157 | File handle from which to untar/unzip/... mathjax |
@@ -80,6 +80,7 b' try:' | |||
|
80 | 80 | import traceback |
|
81 | 81 | import signal |
|
82 | 82 | import codecs |
|
83 | import stat | |
|
83 | 84 | except ImportError: # pragma: no cover |
|
84 | 85 | err = sys.exc_info()[1] |
|
85 | 86 | raise ImportError(str(err) + ''' |
@@ -87,7 +88,7 b' except ImportError: # pragma: no cover' | |||
|
87 | 88 | A critical module was not found. Probably this operating system does not |
|
88 | 89 | support it. Pexpect is intended for UNIX-like operating systems.''') |
|
89 | 90 | |
|
90 |
__version__ = '3. |
|
|
91 | __version__ = '3.3' | |
|
91 | 92 | __revision__ = '' |
|
92 | 93 | __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu', |
|
93 | 94 | 'which', 'split_command_line', '__version__', '__revision__'] |
@@ -284,6 +285,7 b' class spawn(object):' | |||
|
284 | 285 | def _chr(c): |
|
285 | 286 | return bytes([c]) |
|
286 | 287 | linesep = os.linesep.encode('ascii') |
|
288 | crlf = '\r\n'.encode('ascii') | |
|
287 | 289 | |
|
288 | 290 | @staticmethod |
|
289 | 291 | def write_to_stdout(b): |
@@ -296,13 +298,14 b' class spawn(object):' | |||
|
296 | 298 | allowed_string_types = (basestring,) # analysis:ignore |
|
297 | 299 | _chr = staticmethod(chr) |
|
298 | 300 | linesep = os.linesep |
|
301 | crlf = '\r\n' | |
|
299 | 302 | write_to_stdout = sys.stdout.write |
|
300 | 303 | |
|
301 | 304 | encoding = None |
|
302 | 305 | |
|
303 | 306 | def __init__(self, command, args=[], timeout=30, maxread=2000, |
|
304 | 307 | searchwindowsize=None, logfile=None, cwd=None, env=None, |
|
305 | ignore_sighup=True): | |
|
308 | ignore_sighup=True, echo=True): | |
|
306 | 309 | |
|
307 | 310 | '''This is the constructor. The command parameter may be a string that |
|
308 | 311 | includes a command and any arguments to the command. For example:: |
@@ -415,7 +418,16 b' class spawn(object):' | |||
|
415 | 418 | signalstatus will store the signal value and exitstatus will be None. |
|
416 | 419 | If you need more detail you can also read the self.status member which |
|
417 | 420 | stores the status returned by os.waitpid. You can interpret this using |
|
418 |
os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. |
|
|
421 | os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. | |
|
422 | ||
|
423 | The echo attribute may be set to False to disable echoing of input. | |
|
424 | As a pseudo-terminal, all input echoed by the "keyboard" (send() | |
|
425 | or sendline()) will be repeated to output. For many cases, it is | |
|
426 | not desirable to have echo enabled, and it may be later disabled | |
|
427 | using setecho(False) followed by waitnoecho(). However, for some | |
|
428 | platforms such as Solaris, this is not possible, and should be | |
|
429 | disabled immediately on spawn. | |
|
430 | ''' | |
|
419 | 431 | |
|
420 | 432 | self.STDIN_FILENO = pty.STDIN_FILENO |
|
421 | 433 | self.STDOUT_FILENO = pty.STDOUT_FILENO |
@@ -437,7 +449,7 b' class spawn(object):' | |||
|
437 | 449 | self.status = None |
|
438 | 450 | self.flag_eof = False |
|
439 | 451 | self.pid = None |
|
440 |
# the chil |
|
|
452 | # the child file descriptor is initially closed | |
|
441 | 453 | self.child_fd = -1 |
|
442 | 454 | self.timeout = timeout |
|
443 | 455 | self.delimiter = EOF |
@@ -466,16 +478,30 b' class spawn(object):' | |||
|
466 | 478 | self.closed = True |
|
467 | 479 | self.cwd = cwd |
|
468 | 480 | self.env = env |
|
481 | self.echo = echo | |
|
469 | 482 | self.ignore_sighup = ignore_sighup |
|
483 | _platform = sys.platform.lower() | |
|
470 | 484 | # This flags if we are running on irix |
|
471 |
self.__irix_hack = |
|
|
485 | self.__irix_hack = _platform.startswith('irix') | |
|
472 | 486 | # Solaris uses internal __fork_pty(). All others use pty.fork(). |
|
473 | if ((sys.platform.lower().find('solaris') >= 0) | |
|
474 | or (sys.platform.lower().find('sunos5') >= 0)): | |
|
475 | self.use_native_pty_fork = False | |
|
476 | else: | |
|
477 | self.use_native_pty_fork = True | |
|
478 | ||
|
487 | self.use_native_pty_fork = not ( | |
|
488 | _platform.startswith('solaris') or | |
|
489 | _platform.startswith('sunos')) | |
|
490 | # inherit EOF and INTR definitions from controlling process. | |
|
491 | try: | |
|
492 | from termios import VEOF, VINTR | |
|
493 | fd = sys.__stdin__.fileno() | |
|
494 | self._INTR = ord(termios.tcgetattr(fd)[6][VINTR]) | |
|
495 | self._EOF = ord(termios.tcgetattr(fd)[6][VEOF]) | |
|
496 | except (ImportError, OSError, IOError, termios.error): | |
|
497 | # unless the controlling process is also not a terminal, | |
|
498 | # such as cron(1). Fall-back to using CEOF and CINTR. | |
|
499 | try: | |
|
500 | from termios import CEOF, CINTR | |
|
501 | (self._INTR, self._EOF) = (CINTR, CEOF) | |
|
502 | except ImportError: | |
|
503 | # ^C, ^D | |
|
504 | (self._INTR, self._EOF) = (3, 4) | |
|
479 | 505 | # Support subclasses that do not use command or args. |
|
480 | 506 | if command is None: |
|
481 | 507 | self.command = None |
@@ -599,33 +625,39 b' class spawn(object):' | |||
|
599 | 625 | if self.use_native_pty_fork: |
|
600 | 626 | try: |
|
601 | 627 | self.pid, self.child_fd = pty.fork() |
|
602 | except OSError: | |
|
628 | except OSError: # pragma: no cover | |
|
603 | 629 | err = sys.exc_info()[1] |
|
604 | 630 | raise ExceptionPexpect('pty.fork() failed: ' + str(err)) |
|
605 | 631 | else: |
|
606 | 632 | # Use internal __fork_pty |
|
607 | 633 | self.pid, self.child_fd = self.__fork_pty() |
|
608 | 634 | |
|
609 | if self.pid == 0: | |
|
635 | # Some platforms must call setwinsize() and setecho() from the | |
|
636 | # child process, and others from the master process. We do both, | |
|
637 | # allowing IOError for either. | |
|
638 | ||
|
639 | if self.pid == pty.CHILD: | |
|
610 | 640 | # Child |
|
641 | self.child_fd = self.STDIN_FILENO | |
|
642 | ||
|
643 | # set default window size of 24 rows by 80 columns | |
|
611 | 644 | try: |
|
612 | # used by setwinsize() | |
|
613 | self.child_fd = sys.stdout.fileno() | |
|
614 | 645 | self.setwinsize(24, 80) |
|
615 | # which exception, shouldnt' we catch explicitly .. ? | |
|
616 | except: | |
|
617 | # Some platforms do not like setwinsize (Cygwin). | |
|
618 | # This will cause problem when running applications that | |
|
619 | # are very picky about window size. | |
|
620 | # This is a serious limitation, but not a show stopper. | |
|
621 |
|
|
|
646 | except IOError as err: | |
|
647 | if err.args[0] not in (errno.EINVAL, errno.ENOTTY): | |
|
648 | raise | |
|
649 | ||
|
650 | # disable echo if spawn argument echo was unset | |
|
651 | if not self.echo: | |
|
652 | try: | |
|
653 | self.setecho(self.echo) | |
|
654 | except (IOError, termios.error) as err: | |
|
655 | if err.args[0] not in (errno.EINVAL, errno.ENOTTY): | |
|
656 | raise | |
|
657 | ||
|
622 | 658 | # Do not allow child to inherit open file descriptors from parent. |
|
623 | 659 | max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0] |
|
624 |
|
|
|
625 | try: | |
|
626 | os.close(i) | |
|
627 | except OSError: | |
|
628 | pass | |
|
660 | os.closerange(3, max_fd) | |
|
629 | 661 | |
|
630 | 662 | if self.ignore_sighup: |
|
631 | 663 | signal.signal(signal.SIGHUP, signal.SIG_IGN) |
@@ -638,6 +670,13 b' class spawn(object):' | |||
|
638 | 670 | os.execvpe(self.command, self.args, self.env) |
|
639 | 671 | |
|
640 | 672 | # Parent |
|
673 | try: | |
|
674 | self.setwinsize(24, 80) | |
|
675 | except IOError as err: | |
|
676 | if err.args[0] not in (errno.EINVAL, errno.ENOTTY): | |
|
677 | raise | |
|
678 | ||
|
679 | ||
|
641 | 680 | self.terminated = False |
|
642 | 681 | self.closed = False |
|
643 | 682 | |
@@ -660,19 +699,15 b' class spawn(object):' | |||
|
660 | 699 | raise ExceptionPexpect("Could not open with os.openpty().") |
|
661 | 700 | |
|
662 | 701 | pid = os.fork() |
|
663 |
if pid |
|
|
664 | raise ExceptionPexpect("Failed os.fork().") | |
|
665 | elif pid == 0: | |
|
702 | if pid == pty.CHILD: | |
|
666 | 703 | # Child. |
|
667 | 704 | os.close(parent_fd) |
|
668 | 705 | self.__pty_make_controlling_tty(child_fd) |
|
669 | 706 | |
|
670 |
os.dup2(child_fd, |
|
|
671 |
os.dup2(child_fd, |
|
|
672 |
os.dup2(child_fd, |
|
|
707 | os.dup2(child_fd, self.STDIN_FILENO) | |
|
708 | os.dup2(child_fd, self.STDOUT_FILENO) | |
|
709 | os.dup2(child_fd, self.STDERR_FILENO) | |
|
673 | 710 | |
|
674 | if child_fd > 2: | |
|
675 | os.close(child_fd) | |
|
676 | 711 | else: |
|
677 | 712 | # Parent. |
|
678 | 713 | os.close(child_fd) |
@@ -686,45 +721,37 b' class spawn(object):' | |||
|
686 | 721 | |
|
687 | 722 | child_name = os.ttyname(tty_fd) |
|
688 | 723 | |
|
689 |
# Disconnect from controlling tty |
|
|
724 | # Disconnect from controlling tty, if any. Raises OSError of ENXIO | |
|
725 | # if there was no controlling tty to begin with, such as when | |
|
726 | # executed by a cron(1) job. | |
|
690 | 727 | try: |
|
691 | 728 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) |
|
692 | if fd >= 0: | |
|
693 | 729 |
|
|
694 | # which exception, shouldnt' we catch explicitly .. ? | |
|
695 | except: | |
|
696 | # Already disconnected. This happens if running inside cron. | |
|
697 | pass | |
|
730 | except OSError as err: | |
|
731 | if err.errno != errno.ENXIO: | |
|
732 | raise | |
|
698 | 733 | |
|
699 | 734 | os.setsid() |
|
700 | 735 | |
|
701 | # Verify we are disconnected from controlling tty | |
|
702 | # by attempting to open it again. | |
|
736 | # Verify we are disconnected from controlling tty by attempting to open | |
|
737 | # it again. We expect that OSError of ENXIO should always be raised. | |
|
703 | 738 | try: |
|
704 | 739 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) |
|
705 | if fd >= 0: | |
|
706 | 740 |
|
|
707 | raise ExceptionPexpect('Failed to disconnect from ' + | |
|
708 | 'controlling tty. It is still possible to open /dev/tty.') | |
|
709 | # which exception, shouldnt' we catch explicitly .. ? | |
|
710 | except: | |
|
711 | # Good! We are disconnected from a controlling tty. | |
|
712 | pass | |
|
741 | raise ExceptionPexpect("OSError of errno.ENXIO should be raised.") | |
|
742 | except OSError as err: | |
|
743 | if err.errno != errno.ENXIO: | |
|
744 | raise | |
|
713 | 745 | |
|
714 | 746 | # Verify we can open child pty. |
|
715 | 747 | fd = os.open(child_name, os.O_RDWR) |
|
716 | if fd < 0: | |
|
717 | raise ExceptionPexpect("Could not open child pty, " + child_name) | |
|
718 | else: | |
|
719 | 748 |
|
|
720 | 749 | |
|
721 | 750 | # Verify we now have a controlling tty. |
|
722 | 751 | fd = os.open("/dev/tty", os.O_WRONLY) |
|
723 | if fd < 0: | |
|
724 | raise ExceptionPexpect("Could not open controlling tty, /dev/tty") | |
|
725 | else: | |
|
726 | 752 |
|
|
727 | 753 | |
|
754 | ||
|
728 | 755 | def fileno(self): |
|
729 | 756 | '''This returns the file descriptor of the pty for the child. |
|
730 | 757 | ''' |
@@ -757,7 +784,12 b' class spawn(object):' | |||
|
757 | 784 | |
|
758 | 785 | def isatty(self): |
|
759 | 786 | '''This returns True if the file descriptor is open and connected to a |
|
760 |
tty(-like) device, else False. |
|
|
787 | tty(-like) device, else False. | |
|
788 | ||
|
789 | On SVR4-style platforms implementing streams, such as SunOS and HP-UX, | |
|
790 | the child pty may not appear as a terminal device. This means | |
|
791 | methods such as setecho(), setwinsize(), getwinsize() may raise an | |
|
792 | IOError. ''' | |
|
761 | 793 | |
|
762 | 794 | return os.isatty(self.child_fd) |
|
763 | 795 | |
@@ -794,12 +826,20 b' class spawn(object):' | |||
|
794 | 826 | def getecho(self): |
|
795 | 827 | '''This returns the terminal echo mode. This returns True if echo is |
|
796 | 828 | on or False if echo is off. Child applications that are expecting you |
|
797 |
to enter a password often set ECHO False. See waitnoecho(). |
|
|
829 | to enter a password often set ECHO False. See waitnoecho(). | |
|
830 | ||
|
831 | Not supported on platforms where ``isatty()`` returns False. ''' | |
|
798 | 832 | |
|
833 | try: | |
|
799 | 834 | attr = termios.tcgetattr(self.child_fd) |
|
800 | if attr[3] & termios.ECHO: | |
|
801 | return True | |
|
802 | return False | |
|
835 | except termios.error as err: | |
|
836 | errmsg = 'getecho() may not be called on this platform' | |
|
837 | if err.args[0] == errno.EINVAL: | |
|
838 | raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) | |
|
839 | raise | |
|
840 | ||
|
841 | self.echo = bool(attr[3] & termios.ECHO) | |
|
842 | return self.echo | |
|
803 | 843 | |
|
804 | 844 | def setecho(self, state): |
|
805 | 845 | '''This sets the terminal echo mode on or off. Note that anything the |
@@ -829,18 +869,35 b' class spawn(object):' | |||
|
829 | 869 | p.expect(['1234']) |
|
830 | 870 | p.expect(['abcd']) |
|
831 | 871 | p.expect(['wxyz']) |
|
872 | ||
|
873 | ||
|
874 | Not supported on platforms where ``isatty()`` returns False. | |
|
832 | 875 | ''' |
|
833 | 876 | |
|
834 | self.child_fd | |
|
877 | errmsg = 'setecho() may not be called on this platform' | |
|
878 | ||
|
879 | try: | |
|
835 | 880 | attr = termios.tcgetattr(self.child_fd) |
|
881 | except termios.error as err: | |
|
882 | if err.args[0] == errno.EINVAL: | |
|
883 | raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) | |
|
884 | raise | |
|
885 | ||
|
836 | 886 | if state: |
|
837 | 887 | attr[3] = attr[3] | termios.ECHO |
|
838 | 888 | else: |
|
839 | 889 | attr[3] = attr[3] & ~termios.ECHO |
|
840 | # I tried TCSADRAIN and TCSAFLUSH, but | |
|
841 | # these were inconsistent and blocked on some platforms. | |
|
842 | # TCSADRAIN would probably be ideal if it worked. | |
|
890 | ||
|
891 | try: | |
|
892 | # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and | |
|
893 | # blocked on some platforms. TCSADRAIN would probably be ideal. | |
|
843 | 894 | termios.tcsetattr(self.child_fd, termios.TCSANOW, attr) |
|
895 | except IOError as err: | |
|
896 | if err.args[0] == errno.EINVAL: | |
|
897 | raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) | |
|
898 | raise | |
|
899 | ||
|
900 | self.echo = state | |
|
844 | 901 | |
|
845 | 902 | def _log(self, s, direction): |
|
846 | 903 | if self.logfile is not None: |
@@ -913,12 +970,14 b' class spawn(object):' | |||
|
913 | 970 | if self.child_fd in r: |
|
914 | 971 | try: |
|
915 | 972 | s = os.read(self.child_fd, size) |
|
916 | except OSError: | |
|
917 | # Linux does this | |
|
973 | except OSError as err: | |
|
974 | if err.args[0] == errno.EIO: | |
|
975 | # Linux-style EOF | |
|
918 | 976 | self.flag_eof = True |
|
919 | 977 | raise EOF('End Of File (EOF). Exception style platform.') |
|
978 | raise | |
|
920 | 979 | if s == b'': |
|
921 |
# BSD |
|
|
980 | # BSD-style EOF | |
|
922 | 981 | self.flag_eof = True |
|
923 | 982 | raise EOF('End Of File (EOF). Empty string style platform.') |
|
924 | 983 | |
@@ -926,7 +985,7 b' class spawn(object):' | |||
|
926 | 985 | self._log(s, 'read') |
|
927 | 986 | return s |
|
928 | 987 | |
|
929 | raise ExceptionPexpect('Reached an unexpected state.') | |
|
988 | raise ExceptionPexpect('Reached an unexpected state.') # pragma: no cover | |
|
930 | 989 | |
|
931 | 990 | def read(self, size=-1): |
|
932 | 991 | '''This reads at most "size" bytes from the file (less if the read hits |
@@ -972,9 +1031,9 b' class spawn(object):' | |||
|
972 | 1031 | if size == 0: |
|
973 | 1032 | return self.string_type() |
|
974 | 1033 | # delimiter default is EOF |
|
975 |
index = self.expect([ |
|
|
1034 | index = self.expect([self.crlf, self.delimiter]) | |
|
976 | 1035 | if index == 0: |
|
977 |
return self.before + |
|
|
1036 | return self.before + self.crlf | |
|
978 | 1037 | else: |
|
979 | 1038 | return self.before |
|
980 | 1039 | |
@@ -1075,40 +1134,14 b' class spawn(object):' | |||
|
1075 | 1134 | It is the responsibility of the caller to ensure the eof is sent at the |
|
1076 | 1135 | beginning of a line. ''' |
|
1077 | 1136 | |
|
1078 | ### Hmmm... how do I send an EOF? | |
|
1079 | ###C if ((m = write(pty, *buf, p - *buf)) < 0) | |
|
1080 | ###C return (errno == EWOULDBLOCK) ? n : -1; | |
|
1081 | #fd = sys.stdin.fileno() | |
|
1082 | #old = termios.tcgetattr(fd) # remember current state | |
|
1083 | #attr = termios.tcgetattr(fd) | |
|
1084 | #attr[3] = attr[3] | termios.ICANON # ICANON must be set to see EOF | |
|
1085 | #try: # use try/finally to ensure state gets restored | |
|
1086 | # termios.tcsetattr(fd, termios.TCSADRAIN, attr) | |
|
1087 | # if hasattr(termios, 'CEOF'): | |
|
1088 | # os.write(self.child_fd, '%c' % termios.CEOF) | |
|
1089 | # else: | |
|
1090 | # # Silly platform does not define CEOF so assume CTRL-D | |
|
1091 | # os.write(self.child_fd, '%c' % 4) | |
|
1092 | #finally: # restore state | |
|
1093 | # termios.tcsetattr(fd, termios.TCSADRAIN, old) | |
|
1094 | if hasattr(termios, 'VEOF'): | |
|
1095 | char = ord(termios.tcgetattr(self.child_fd)[6][termios.VEOF]) | |
|
1096 | else: | |
|
1097 | # platform does not define VEOF so assume CTRL-D | |
|
1098 | char = 4 | |
|
1099 | self.send(self._chr(char)) | |
|
1137 | self.send(self._chr(self._EOF)) | |
|
1100 | 1138 | |
|
1101 | 1139 | def sendintr(self): |
|
1102 | 1140 | |
|
1103 | 1141 | '''This sends a SIGINT to the child. It does not require |
|
1104 | 1142 | the SIGINT to be the first character on a line. ''' |
|
1105 | 1143 | |
|
1106 | if hasattr(termios, 'VINTR'): | |
|
1107 | char = ord(termios.tcgetattr(self.child_fd)[6][termios.VINTR]) | |
|
1108 | else: | |
|
1109 | # platform does not define VINTR so assume CTRL-C | |
|
1110 | char = 3 | |
|
1111 | self.send(self._chr(char)) | |
|
1144 | self.send(self._chr(self._INTR)) | |
|
1112 | 1145 | |
|
1113 | 1146 | def eof(self): |
|
1114 | 1147 | |
@@ -1181,7 +1214,7 b' class spawn(object):' | |||
|
1181 | 1214 | self.exitstatus = None |
|
1182 | 1215 | self.signalstatus = os.WTERMSIG(status) |
|
1183 | 1216 | self.terminated = True |
|
1184 | elif os.WIFSTOPPED(status): | |
|
1217 | elif os.WIFSTOPPED(status): # pragma: no cover | |
|
1185 | 1218 | # You can't call wait() on a child process in the stopped state. |
|
1186 | 1219 | raise ExceptionPexpect('Called wait() on a stopped child ' + |
|
1187 | 1220 | 'process. This is not supported. Is some other ' + |
@@ -1201,7 +1234,7 b' class spawn(object):' | |||
|
1201 | 1234 | |
|
1202 | 1235 | if self.flag_eof: |
|
1203 | 1236 | # This is for Linux, which requires the blocking form |
|
1204 |
# of waitpid to |
|
|
1237 | # of waitpid to get the status of a defunct process. | |
|
1205 | 1238 | # This is super-lame. The flag_eof would have been set |
|
1206 | 1239 | # in read_nonblocking(), so this should be safe. |
|
1207 | 1240 | waitpid_options = 0 |
@@ -1229,7 +1262,7 b' class spawn(object):' | |||
|
1229 | 1262 | try: |
|
1230 | 1263 | ### os.WNOHANG) # Solaris! |
|
1231 | 1264 | pid, status = os.waitpid(self.pid, waitpid_options) |
|
1232 | except OSError as e: | |
|
1265 | except OSError as e: # pragma: no cover | |
|
1233 | 1266 | # This should never happen... |
|
1234 | 1267 | if e.errno == errno.ECHILD: |
|
1235 | 1268 | raise ExceptionPexpect('isalive() encountered condition ' + |
@@ -1643,10 +1676,14 b' class spawn(object):' | |||
|
1643 | 1676 | if self.child_fd in r: |
|
1644 | 1677 | try: |
|
1645 | 1678 | data = self.__interact_read(self.child_fd) |
|
1646 | except OSError as e: | |
|
1647 | # The subprocess may have closed before we get to reading it | |
|
1648 | if e.errno != errno.EIO: | |
|
1679 | except OSError as err: | |
|
1680 | if err.args[0] == errno.EIO: | |
|
1681 | # Linux-style EOF | |
|
1682 | break | |
|
1649 | 1683 |
|
|
1684 | if data == b'': | |
|
1685 | # BSD-style EOF | |
|
1686 | break | |
|
1650 | 1687 | if output_filter: |
|
1651 | 1688 | data = output_filter(data) |
|
1652 | 1689 | if self.logfile is not None: |
@@ -1695,7 +1732,7 b' class spawn(object):' | |||
|
1695 | 1732 | ############################################################################## |
|
1696 | 1733 | # The following methods are no longer supported or allowed. |
|
1697 | 1734 | |
|
1698 | def setmaxread(self, maxread): | |
|
1735 | def setmaxread(self, maxread): # pragma: no cover | |
|
1699 | 1736 | |
|
1700 | 1737 | '''This method is no longer supported or allowed. I don't like getters |
|
1701 | 1738 | and setters without a good reason. ''' |
@@ -1704,7 +1741,7 b' class spawn(object):' | |||
|
1704 | 1741 | 'or allowed. Just assign a value to the ' + |
|
1705 | 1742 | 'maxread member variable.') |
|
1706 | 1743 | |
|
1707 | def setlog(self, fileobject): | |
|
1744 | def setlog(self, fileobject): # pragma: no cover | |
|
1708 | 1745 | |
|
1709 | 1746 | '''This method is no longer supported or allowed. |
|
1710 | 1747 | ''' |
@@ -1732,11 +1769,13 b' class spawnu(spawn):' | |||
|
1732 | 1769 | allowed_string_types = (str, ) |
|
1733 | 1770 | _chr = staticmethod(chr) |
|
1734 | 1771 | linesep = os.linesep |
|
1772 | crlf = '\r\n' | |
|
1735 | 1773 | else: |
|
1736 | 1774 | string_type = unicode |
|
1737 | 1775 | allowed_string_types = (unicode, ) |
|
1738 | 1776 | _chr = staticmethod(unichr) |
|
1739 | 1777 | linesep = os.linesep.decode('ascii') |
|
1778 | crlf = '\r\n'.decode('ascii') | |
|
1740 | 1779 | # This can handle unicode in both Python 2 and 3 |
|
1741 | 1780 | write_to_stdout = sys.stdout.write |
|
1742 | 1781 | |
@@ -1959,15 +1998,55 b' class searcher_re(object):' | |||
|
1959 | 1998 | return best_index |
|
1960 | 1999 | |
|
1961 | 2000 | |
|
1962 | def which(filename): | |
|
2001 | def is_executable_file(path): | |
|
2002 | """Checks that path is an executable regular file (or a symlink to a file). | |
|
2003 | ||
|
2004 | This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``, but | |
|
2005 | on some platforms :func:`os.access` gives us the wrong answer, so this | |
|
2006 | checks permission bits directly. | |
|
2007 | """ | |
|
2008 | # follow symlinks, | |
|
2009 | fpath = os.path.realpath(path) | |
|
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 | |
|
1963 | 2020 | |
|
2021 | # get file mode using os.stat, and check if `other', | |
|
2022 | # that is anybody, may read and execute. | |
|
2023 | mode = os.stat(fpath).st_mode | |
|
2024 | if mode & stat.S_IROTH and mode & stat.S_IXOTH: | |
|
2025 | return True | |
|
2026 | ||
|
2027 | # get current user's group ids, and check if `group', | |
|
2028 | # when matching ours, may read and execute. | |
|
2029 | user_gids = os.getgroups() + [os.getgid()] | |
|
2030 | if (os.stat(fpath).st_gid in user_gids and | |
|
2031 | mode & stat.S_IRGRP and mode & stat.S_IXGRP): | |
|
2032 | return True | |
|
2033 | ||
|
2034 | # finally, if file owner matches our effective userid, | |
|
2035 | # check if `user', may read and execute. | |
|
2036 | user_gids = os.getgroups() + [os.getgid()] | |
|
2037 | if (os.stat(fpath).st_uid == os.geteuid() and | |
|
2038 | mode & stat.S_IRUSR and mode & stat.S_IXUSR): | |
|
2039 | return True | |
|
2040 | ||
|
2041 | return False | |
|
2042 | ||
|
2043 | def which(filename): | |
|
1964 | 2044 | '''This takes a given filename; tries to find it in the environment path; |
|
1965 | 2045 | then checks if it is executable. This returns the full path to the filename |
|
1966 | 2046 | if found and executable. Otherwise this returns None.''' |
|
1967 | 2047 | |
|
1968 | 2048 | # Special case where filename contains an explicit path. |
|
1969 | if os.path.dirname(filename) != '': | |
|
1970 | if os.access(filename, os.X_OK): | |
|
2049 | if os.path.dirname(filename) != '' and is_executable_file(filename): | |
|
1971 | 2050 |
|
|
1972 | 2051 | if 'PATH' not in os.environ or os.environ['PATH'] == '': |
|
1973 | 2052 | p = os.defpath |
@@ -1976,7 +2055,7 b' def which(filename):' | |||
|
1976 | 2055 | pathlist = p.split(os.pathsep) |
|
1977 | 2056 | for path in pathlist: |
|
1978 | 2057 | ff = os.path.join(path, filename) |
|
1979 | if os.access(ff, os.X_OK): | |
|
2058 | if is_executable_file(ff): | |
|
1980 | 2059 | return ff |
|
1981 | 2060 | return None |
|
1982 | 2061 | |
@@ -2041,4 +2120,4 b' def split_command_line(command_line):' | |||
|
2041 | 2120 | arg_list.append(arg) |
|
2042 | 2121 | return arg_list |
|
2043 | 2122 | |
|
2044 | # vi:set sr et ts=4 sw=4 ft=python : | |
|
2123 | # vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent : |
@@ -13,19 +13,18 b' Developers of the IPython Notebook will need to install the following tools:' | |||
|
13 | 13 | |
|
14 | 14 | We are moving to a model where our JavaScript dependencies are managed using |
|
15 | 15 | [bower](http://bower.io/). These packages are installed in `static/components` |
|
16 | and committed into our git repo. Our dependencies are described in the file | |
|
16 | and committed into a separate git repo [ipython/ipython-components](ipython/ipython-components). | |
|
17 | Our dependencies are described in the file | |
|
17 | 18 | `static/components/bower.json`. To update our bower packages, run `fab update` |
|
18 | 19 | in this directory. |
|
19 | 20 | |
|
20 | Because CodeMirror does not use proper semantic versioning for its GitHub tags, | |
|
21 | we maintain our own fork of CodeMirror that is used with bower. This fork should | |
|
22 | track the upstream CodeMirror exactly; the only difference is that we are adding | |
|
23 | semantic versioned tags to our repo. | |
|
24 | ||
|
25 | 21 | ## less |
|
26 | 22 | |
|
27 | 23 | If you edit our `.less` files you will need to run the less compiler to build |
|
28 |
our minified css files. This can be done by running `fab css` from this directory |
|
|
24 | our minified css files. This can be done by running `fab css` from this directory, | |
|
25 | or `python setup.py css` from the root of the repository. | |
|
26 | If you are working frequently with `.less` files please consider installing git hooks that | |
|
27 | rebuild the css files and corresponding maps in `${RepoRoot}/git-hooks/install-hooks.sh`. | |
|
29 | 28 | |
|
30 | 29 | ## JavaScript Documentation |
|
31 | 30 |
@@ -1,21 +1,7 b'' | |||
|
1 | """Base Tornado handlers for the notebook. | |
|
2 | ||
|
3 | Authors: | |
|
4 | ||
|
5 | * Brian Granger | |
|
6 | """ | |
|
7 | ||
|
8 | #----------------------------------------------------------------------------- | |
|
9 | # Copyright (C) 2011 The IPython Development Team | |
|
10 | # | |
|
11 | # Distributed under the terms of the BSD License. The full license is in | |
|
12 | # the file COPYING, distributed as part of this software. | |
|
13 | #----------------------------------------------------------------------------- | |
|
14 | ||
|
15 | #----------------------------------------------------------------------------- | |
|
16 | # Imports | |
|
17 | #----------------------------------------------------------------------------- | |
|
1 | """Base Tornado handlers for the notebook server.""" | |
|
18 | 2 | |
|
3 | # Copyright (c) IPython Development Team. | |
|
4 | # Distributed under the terms of the Modified BSD License. | |
|
19 | 5 | |
|
20 | 6 | import functools |
|
21 | 7 | import json |
@@ -41,7 +27,7 b' except ImportError:' | |||
|
41 | 27 | from IPython.config import Application |
|
42 | 28 | from IPython.utils.path import filefind |
|
43 | 29 | from IPython.utils.py3compat import string_types |
|
44 | from IPython.html.utils import is_hidden | |
|
30 | from IPython.html.utils import is_hidden, url_path_join, url_escape | |
|
45 | 31 | |
|
46 | 32 | #----------------------------------------------------------------------------- |
|
47 | 33 | # Top-level handlers |
@@ -53,6 +39,10 b' class AuthenticatedHandler(web.RequestHandler):' | |||
|
53 | 39 | |
|
54 | 40 | def set_default_headers(self): |
|
55 | 41 | headers = self.settings.get('headers', {}) |
|
42 | ||
|
43 | if "X-Frame-Options" not in headers: | |
|
44 | headers["X-Frame-Options"] = "SAMEORIGIN" | |
|
45 | ||
|
56 | 46 | for header_name,value in headers.items() : |
|
57 | 47 | try: |
|
58 | 48 | self.set_header(header_name, value) |
@@ -138,6 +128,10 b' class IPythonHandler(AuthenticatedHandler):' | |||
|
138 | 128 | def base_url(self): |
|
139 | 129 | return self.settings.get('base_url', '/') |
|
140 | 130 | |
|
131 | @property | |
|
132 | def ws_url(self): | |
|
133 | return self.settings.get('websocket_url', '') | |
|
134 | ||
|
141 | 135 | #--------------------------------------------------------------- |
|
142 | 136 | # Manager objects |
|
143 | 137 | #--------------------------------------------------------------- |
@@ -147,8 +141,8 b' class IPythonHandler(AuthenticatedHandler):' | |||
|
147 | 141 | return self.settings['kernel_manager'] |
|
148 | 142 | |
|
149 | 143 | @property |
|
150 |
def |
|
|
151 |
return self.settings[' |
|
|
144 | def contents_manager(self): | |
|
145 | return self.settings['contents_manager'] | |
|
152 | 146 | |
|
153 | 147 | @property |
|
154 | 148 | def cluster_manager(self): |
@@ -162,9 +156,47 b' class IPythonHandler(AuthenticatedHandler):' | |||
|
162 | 156 | def kernel_spec_manager(self): |
|
163 | 157 | return self.settings['kernel_spec_manager'] |
|
164 | 158 | |
|
159 | #--------------------------------------------------------------- | |
|
160 | # CORS | |
|
161 | #--------------------------------------------------------------- | |
|
162 | ||
|
165 | 163 | @property |
|
166 |
def |
|
|
167 | return self.notebook_manager.notebook_dir | |
|
164 | def allow_origin(self): | |
|
165 | """Normal Access-Control-Allow-Origin""" | |
|
166 | return self.settings.get('allow_origin', '') | |
|
167 | ||
|
168 | @property | |
|
169 | def allow_origin_pat(self): | |
|
170 | """Regular expression version of allow_origin""" | |
|
171 | return self.settings.get('allow_origin_pat', None) | |
|
172 | ||
|
173 | @property | |
|
174 | def allow_credentials(self): | |
|
175 | """Whether to set Access-Control-Allow-Credentials""" | |
|
176 | return self.settings.get('allow_credentials', False) | |
|
177 | ||
|
178 | def set_default_headers(self): | |
|
179 | """Add CORS headers, if defined""" | |
|
180 | super(IPythonHandler, self).set_default_headers() | |
|
181 | if self.allow_origin: | |
|
182 | self.set_header("Access-Control-Allow-Origin", self.allow_origin) | |
|
183 | elif self.allow_origin_pat: | |
|
184 | origin = self.get_origin() | |
|
185 | if origin and self.allow_origin_pat.match(origin): | |
|
186 | self.set_header("Access-Control-Allow-Origin", origin) | |
|
187 | if self.allow_credentials: | |
|
188 | self.set_header("Access-Control-Allow-Credentials", 'true') | |
|
189 | ||
|
190 | def get_origin(self): | |
|
191 | # Handle WebSocket Origin naming convention differences | |
|
192 | # The difference between version 8 and 13 is that in 8 the | |
|
193 | # client sends a "Sec-Websocket-Origin" header and in 13 it's | |
|
194 | # simply "Origin". | |
|
195 | if "Origin" in self.request.headers: | |
|
196 | origin = self.request.headers.get("Origin") | |
|
197 | else: | |
|
198 | origin = self.request.headers.get("Sec-Websocket-Origin", None) | |
|
199 | return origin | |
|
168 | 200 | |
|
169 | 201 | #--------------------------------------------------------------- |
|
170 | 202 | # template rendering |
@@ -183,6 +215,7 b' class IPythonHandler(AuthenticatedHandler):' | |||
|
183 | 215 | def template_namespace(self): |
|
184 | 216 | return dict( |
|
185 | 217 | base_url=self.base_url, |
|
218 | ws_url=self.ws_url, | |
|
186 | 219 | logged_in=self.logged_in, |
|
187 | 220 | login_available=self.login_available, |
|
188 | 221 | static_url=self.static_url, |
@@ -202,12 +235,13 b' class IPythonHandler(AuthenticatedHandler):' | |||
|
202 | 235 | raise web.HTTPError(400, u'Invalid JSON in body of request') |
|
203 | 236 | return model |
|
204 | 237 | |
|
205 |
def |
|
|
238 | def write_error(self, status_code, **kwargs): | |
|
206 | 239 | """render custom error pages""" |
|
207 |
exc |
|
|
240 | exc_info = kwargs.get('exc_info') | |
|
208 | 241 | message = '' |
|
209 | 242 | status_message = responses.get(status_code, 'Unknown HTTP Error') |
|
210 |
if exc |
|
|
243 | if exc_info: | |
|
244 | exception = exc_info[1] | |
|
211 | 245 | # get the custom message, if defined |
|
212 | 246 | try: |
|
213 | 247 | message = exception.log_message % exception.args |
@@ -227,13 +261,16 b' class IPythonHandler(AuthenticatedHandler):' | |||
|
227 | 261 | exception=exception, |
|
228 | 262 | ) |
|
229 | 263 | |
|
264 | self.set_header('Content-Type', 'text/html') | |
|
230 | 265 | # render the template |
|
231 | 266 | try: |
|
232 | 267 | html = self.render_template('%s.html' % status_code, **ns) |
|
233 | 268 | except TemplateNotFound: |
|
234 | 269 | self.log.debug("No template for %d", status_code) |
|
235 | 270 | html = self.render_template('error.html', **ns) |
|
236 | return html | |
|
271 | ||
|
272 | self.write(html) | |
|
273 | ||
|
237 | 274 | |
|
238 | 275 | |
|
239 | 276 | class Template404(IPythonHandler): |
@@ -372,6 +409,37 b' class TrailingSlashHandler(web.RequestHandler):' | |||
|
372 | 409 | def get(self): |
|
373 | 410 | self.redirect(self.request.uri.rstrip('/')) |
|
374 | 411 | |
|
412 | ||
|
413 | class FilesRedirectHandler(IPythonHandler): | |
|
414 | """Handler for redirecting relative URLs to the /files/ handler""" | |
|
415 | def get(self, path=''): | |
|
416 | cm = self.contents_manager | |
|
417 | if cm.path_exists(path): | |
|
418 | # it's a *directory*, redirect to /tree | |
|
419 | url = url_path_join(self.base_url, 'tree', path) | |
|
420 | else: | |
|
421 | orig_path = path | |
|
422 | # otherwise, redirect to /files | |
|
423 | parts = path.split('/') | |
|
424 | path = '/'.join(parts[:-1]) | |
|
425 | name = parts[-1] | |
|
426 | ||
|
427 | if not cm.file_exists(name=name, path=path) and 'files' in parts: | |
|
428 | # redirect without files/ iff it would 404 | |
|
429 | # this preserves pre-2.0-style 'files/' links | |
|
430 | self.log.warn("Deprecated files/ URL: %s", orig_path) | |
|
431 | parts.remove('files') | |
|
432 | path = '/'.join(parts[:-1]) | |
|
433 | ||
|
434 | if not cm.file_exists(name=name, path=path): | |
|
435 | raise web.HTTPError(404) | |
|
436 | ||
|
437 | url = url_path_join(self.base_url, 'files', path, name) | |
|
438 | url = url_escape(url) | |
|
439 | self.log.debug("Redirecting %s to %s", self.request.path, url) | |
|
440 | self.redirect(url) | |
|
441 | ||
|
442 | ||
|
375 | 443 | #----------------------------------------------------------------------------- |
|
376 | 444 | # URL pattern fragments for re-use |
|
377 | 445 | #----------------------------------------------------------------------------- |
@@ -379,6 +447,8 b' class TrailingSlashHandler(web.RequestHandler):' | |||
|
379 | 447 | path_regex = r"(?P<path>(?:/.*)*)" |
|
380 | 448 | notebook_name_regex = r"(?P<name>[^/]+\.ipynb)" |
|
381 | 449 | notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex) |
|
450 | file_name_regex = r"(?P<name>[^/]+)" | |
|
451 | file_path_regex = "%s/%s" % (path_regex, file_name_regex) | |
|
382 | 452 | |
|
383 | 453 | #----------------------------------------------------------------------------- |
|
384 | 454 | # URL to handler mappings |
@@ -15,6 +15,9 b' try:' | |||
|
15 | 15 | except ImportError: |
|
16 | 16 | from Cookie import SimpleCookie # Py 2 |
|
17 | 17 | import logging |
|
18 | ||
|
19 | import tornado | |
|
20 | from tornado import ioloop | |
|
18 | 21 | from tornado import web |
|
19 | 22 | from tornado import websocket |
|
20 | 23 | |
@@ -27,28 +30,35 b' from .handlers import IPythonHandler' | |||
|
27 | 30 | |
|
28 | 31 | class ZMQStreamHandler(websocket.WebSocketHandler): |
|
29 | 32 | |
|
30 |
def |
|
|
31 | """Check to see that origin and host match in the headers.""" | |
|
33 | def check_origin(self, origin): | |
|
34 | """Check Origin == Host or Access-Control-Allow-Origin. | |
|
32 | 35 | |
|
33 | # The difference between version 8 and 13 is that in 8 the | |
|
34 | # client sends a "Sec-Websocket-Origin" header and in 13 it's | |
|
35 | # simply "Origin". | |
|
36 | if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"): | |
|
37 | origin_header = self.request.headers.get("Sec-Websocket-Origin") | |
|
38 | else: | |
|
39 | origin_header = self.request.headers.get("Origin") | |
|
36 | Tornado >= 4 calls this method automatically, raising 403 if it returns False. | |
|
37 | We call it explicitly in `open` on Tornado < 4. | |
|
38 | """ | |
|
39 | if self.allow_origin == '*': | |
|
40 | return True | |
|
40 | 41 | |
|
41 | 42 | host = self.request.headers.get("Host") |
|
42 | 43 | |
|
43 | 44 | # If no header is provided, assume we can't verify origin |
|
44 |
if(origin |
|
|
45 | if(origin is None or host is None): | |
|
45 | 46 | return False |
|
46 | 47 | |
|
47 | parsed_origin = urlparse(origin_header) | |
|
48 | origin = parsed_origin.netloc | |
|
48 | host_origin = "{0}://{1}".format(self.request.protocol, host) | |
|
49 | 49 | |
|
50 | # Check to see that origin matches host directly, including ports | |
|
51 |
|
|
|
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 | |
|
61 | return False | |
|
52 | 62 | |
|
53 | 63 | def clear_cookie(self, *args, **kwargs): |
|
54 | 64 | """meaningless for websockets""" |
@@ -94,19 +104,41 b' class ZMQStreamHandler(websocket.WebSocketHandler):' | |||
|
94 | 104 | """ |
|
95 | 105 | return True |
|
96 | 106 | |
|
107 | # ping interval for keeping websockets alive (30 seconds) | |
|
108 | WS_PING_INTERVAL = 30000 | |
|
97 | 109 | |
|
98 | 110 | class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): |
|
111 | ping_callback = None | |
|
112 | ||
|
113 | def set_default_headers(self): | |
|
114 | """Undo the set_default_headers in IPythonHandler | |
|
115 | ||
|
116 | which doesn't make sense for websockets | |
|
117 | """ | |
|
118 | pass | |
|
99 | 119 | |
|
100 | 120 | def open(self, kernel_id): |
|
101 | 121 | self.kernel_id = cast_unicode(kernel_id, 'ascii') |
|
102 | 122 | # Check to see that origin matches host directly, including ports |
|
103 | if not self.same_origin(): | |
|
104 | self.log.warn("Cross Origin WebSocket Attempt.") | |
|
105 | raise web.HTTPError(404) | |
|
123 | # Tornado 4 already does CORS checking | |
|
124 | if tornado.version_info[0] < 4: | |
|
125 | if not self.check_origin(self.get_origin()): | |
|
126 | self.log.warn("Cross Origin WebSocket Attempt from %s", self.get_origin()) | |
|
127 | raise web.HTTPError(403) | |
|
106 | 128 | |
|
107 | 129 | self.session = Session(config=self.config) |
|
108 | 130 | self.save_on_message = self.on_message |
|
109 | 131 | self.on_message = self.on_first_message |
|
132 | self.ping_callback = ioloop.PeriodicCallback(self.send_ping, WS_PING_INTERVAL) | |
|
133 | self.ping_callback.start() | |
|
134 | ||
|
135 | def send_ping(self): | |
|
136 | """send a ping to keep the websocket alive""" | |
|
137 | if self.stream.closed() and self.ping_callback is not None: | |
|
138 | self.ping_callback.stop() | |
|
139 | return | |
|
140 | ||
|
141 | self.ping(b'') | |
|
110 | 142 | |
|
111 | 143 | def _inject_cookie_message(self, msg): |
|
112 | 144 | """Inject the first message, which is the document cookie, |
@@ -8,31 +8,65 b' from subprocess import check_output' | |||
|
8 | 8 | |
|
9 | 9 | pjoin = os.path.join |
|
10 | 10 | static_dir = 'static' |
|
11 |
components_dir = |
|
|
11 | components_dir = pjoin(static_dir, 'components') | |
|
12 | here = os.path.dirname(__file__) | |
|
12 | 13 | |
|
13 |
min_less_version = '1. |
|
|
14 |
max_less_version = '1. |
|
|
14 | min_less_version = '1.7.0' | |
|
15 | max_less_version = '1.8.0' # exclusive | |
|
15 | 16 | |
|
16 | def css(minify=True, verbose=False): | |
|
17 | def _need_css_update(): | |
|
18 | """Does less need to run?""" | |
|
19 | ||
|
20 | static_path = pjoin(here, static_dir) | |
|
21 | css_targets = [ | |
|
22 | pjoin(static_path, 'style', '%s.min.css' % name) | |
|
23 | for name in ('style', 'ipython') | |
|
24 | ] | |
|
25 | css_maps = [t + '.map' for t in css_targets] | |
|
26 | targets = css_targets + css_maps | |
|
27 | if not all(os.path.exists(t) for t in targets): | |
|
28 | # some generated files don't exist | |
|
29 | return True | |
|
30 | earliest_target = sorted(os.stat(t).st_mtime for t in targets)[0] | |
|
31 | ||
|
32 | # check if any .less files are newer than the generated targets | |
|
33 | for (dirpath, dirnames, filenames) in os.walk(static_path): | |
|
34 | for f in filenames: | |
|
35 | if f.endswith('.less'): | |
|
36 | path = pjoin(static_path, dirpath, f) | |
|
37 | timestamp = os.stat(path).st_mtime | |
|
38 | if timestamp > earliest_target: | |
|
39 | return True | |
|
40 | ||
|
41 | return False | |
|
42 | ||
|
43 | def css(minify=False, verbose=False, force=False): | |
|
17 | 44 | """generate the css from less files""" |
|
45 | minify = _to_bool(minify) | |
|
46 | verbose = _to_bool(verbose) | |
|
47 | force = _to_bool(force) | |
|
48 | # minify implies force because it's not the default behavior | |
|
49 | if not force and not minify and not _need_css_update(): | |
|
50 | print("css up-to-date") | |
|
51 | return | |
|
52 | ||
|
18 | 53 | for name in ('style', 'ipython'): |
|
19 | 54 | source = pjoin('style', "%s.less" % name) |
|
20 | 55 | target = pjoin('style', "%s.min.css" % name) |
|
21 | _compile_less(source, target, minify, verbose) | |
|
56 | sourcemap = pjoin('style', "%s.min.css.map" % name) | |
|
57 | _compile_less(source, target, sourcemap, minify, verbose) | |
|
22 | 58 | |
|
23 | 59 | def _to_bool(b): |
|
24 | 60 | if not b in ['True', 'False', True, False]: |
|
25 | 61 | abort('boolean expected, got: %s' % b) |
|
26 | 62 | return (b in ['True', True]) |
|
27 | 63 | |
|
28 | def _compile_less(source, target, minify=True, verbose=False): | |
|
64 | def _compile_less(source, target, sourcemap, minify=True, verbose=False): | |
|
29 | 65 | """Compile a less file by source and target relative to static_dir""" |
|
30 | minify = _to_bool(minify) | |
|
31 | verbose = _to_bool(verbose) | |
|
32 | 66 | min_flag = '-x' if minify is True else '' |
|
33 | 67 | ver_flag = '--verbose' if verbose is True else '' |
|
34 | 68 | |
|
35 | # pin less to 1.4 | |
|
69 | # pin less to version number from above | |
|
36 | 70 | try: |
|
37 | 71 | out = check_output(['lessc', '--version']) |
|
38 | 72 | except OSError as err: |
@@ -45,6 +79,7 b' def _compile_less(source, target, minify=True, verbose=False):' | |||
|
45 | 79 | if V(less_version) >= V(max_less_version): |
|
46 | 80 | raise ValueError("lessc too new: %s >= %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, max_less_version)) |
|
47 | 81 | |
|
82 | static_path = pjoin(here, static_dir) | |
|
48 | 83 | with lcd(static_dir): |
|
49 | local('lessc {min_flag} {ver_flag} {source} {target}'.format(**locals())) | |
|
84 | local('lessc {min_flag} {ver_flag} --source-map={sourcemap} --source-map-basepath={static_path} --source-map-rootpath="../" {source} {target}'.format(**locals())) | |
|
50 | 85 |
@@ -1,10 +1,18 b'' | |||
|
1 | """Tornado handlers for nbconvert.""" | |
|
2 | ||
|
3 | # Copyright (c) IPython Development Team. | |
|
4 | # Distributed under the terms of the Modified BSD License. | |
|
5 | ||
|
1 | 6 | import io |
|
2 | 7 | import os |
|
3 | 8 | import zipfile |
|
4 | 9 | |
|
5 | 10 | from tornado import web |
|
6 | 11 | |
|
7 |
from ..base.handlers import |
|
|
12 | from ..base.handlers import ( | |
|
13 | IPythonHandler, FilesRedirectHandler, | |
|
14 | notebook_path_regex, path_regex, | |
|
15 | ) | |
|
8 | 16 | from IPython.nbformat.current import to_notebook_json |
|
9 | 17 | |
|
10 | 18 | from IPython.utils.py3compat import cast_bytes |
@@ -73,7 +81,7 b' class NbconvertFileHandler(IPythonHandler):' | |||
|
73 | 81 | exporter = get_exporter(format, config=self.config, log=self.log) |
|
74 | 82 | |
|
75 | 83 | path = path.strip('/') |
|
76 |
model = self. |
|
|
84 | model = self.contents_manager.get_model(name=name, path=path) | |
|
77 | 85 | |
|
78 | 86 | self.set_header('Last-Modified', model['last_modified']) |
|
79 | 87 | |
@@ -123,6 +131,7 b' class NbconvertPostHandler(IPythonHandler):' | |||
|
123 | 131 | |
|
124 | 132 | self.finish(output) |
|
125 | 133 | |
|
134 | ||
|
126 | 135 | #----------------------------------------------------------------------------- |
|
127 | 136 | # URL to handler mappings |
|
128 | 137 | #----------------------------------------------------------------------------- |
@@ -134,4 +143,5 b' default_handlers = [' | |||
|
134 | 143 | (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex), |
|
135 | 144 | NbconvertFileHandler), |
|
136 | 145 | (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler), |
|
146 | (r"/nbconvert/html%s" % path_regex, FilesRedirectHandler), | |
|
137 | 147 | ] |
@@ -106,7 +106,7 b' class APITest(NotebookTestBase):' | |||
|
106 | 106 | |
|
107 | 107 | @onlyif_cmds_exist('pandoc') |
|
108 | 108 | def test_from_post(self): |
|
109 |
nbmodel_url = url_path_join(self.base_url(), 'api/ |
|
|
109 | nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb') | |
|
110 | 110 | nbmodel = requests.get(nbmodel_url).json() |
|
111 | 111 | |
|
112 | 112 | r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel) |
@@ -121,7 +121,7 b' class APITest(NotebookTestBase):' | |||
|
121 | 121 | |
|
122 | 122 | @onlyif_cmds_exist('pandoc') |
|
123 | 123 | def test_from_post_zip(self): |
|
124 |
nbmodel_url = url_path_join(self.base_url(), 'api/ |
|
|
124 | nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb') | |
|
125 | 125 | nbmodel = requests.get(nbmodel_url).json() |
|
126 | 126 | |
|
127 | 127 | r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel) |
@@ -1,31 +1,17 b'' | |||
|
1 | """Tornado handlers for the live notebook view. | |
|
1 | """Tornado handlers for the live notebook view.""" | |
|
2 | 2 | |
|
3 | Authors: | |
|
4 | ||
|
5 | * Brian Granger | |
|
6 | """ | |
|
7 | ||
|
8 | #----------------------------------------------------------------------------- | |
|
9 | # Copyright (C) 2011 The IPython Development Team | |
|
10 | # | |
|
11 | # Distributed under the terms of the BSD License. The full license is in | |
|
12 | # the file COPYING, distributed as part of this software. | |
|
13 | #----------------------------------------------------------------------------- | |
|
14 | ||
|
15 | #----------------------------------------------------------------------------- | |
|
16 | # Imports | |
|
17 | #----------------------------------------------------------------------------- | |
|
3 | # Copyright (c) IPython Development Team. | |
|
4 | # Distributed under the terms of the Modified BSD License. | |
|
18 | 5 | |
|
19 | 6 | import os |
|
20 | 7 | from tornado import web |
|
21 | 8 | HTTPError = web.HTTPError |
|
22 | 9 | |
|
23 | from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex | |
|
24 | from ..utils import url_path_join, url_escape | |
|
25 | ||
|
26 | #----------------------------------------------------------------------------- | |
|
27 | # Handlers | |
|
28 | #----------------------------------------------------------------------------- | |
|
10 | from ..base.handlers import ( | |
|
11 | IPythonHandler, FilesRedirectHandler, | |
|
12 | notebook_path_regex, path_regex, | |
|
13 | ) | |
|
14 | from ..utils import url_escape | |
|
29 | 15 | |
|
30 | 16 | |
|
31 | 17 | class NotebookHandler(IPythonHandler): |
@@ -35,17 +21,16 b' class NotebookHandler(IPythonHandler):' | |||
|
35 | 21 | """get renders the notebook template if a name is given, or |
|
36 | 22 | redirects to the '/files/' handler if the name is not given.""" |
|
37 | 23 | path = path.strip('/') |
|
38 |
|
|
|
24 | cm = self.contents_manager | |
|
39 | 25 | if name is None: |
|
40 | 26 | raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri) |
|
41 | 27 | |
|
42 | 28 | # a .ipynb filename was given |
|
43 |
if not |
|
|
29 | if not cm.file_exists(name, path): | |
|
44 | 30 | raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name)) |
|
45 | 31 | name = url_escape(name) |
|
46 | 32 | path = url_escape(path) |
|
47 | 33 | self.write(self.render_template('notebook.html', |
|
48 | project=self.project_dir, | |
|
49 | 34 | notebook_path=path, |
|
50 | 35 | notebook_name=name, |
|
51 | 36 | kill_kernel=False, |
@@ -53,30 +38,6 b' class NotebookHandler(IPythonHandler):' | |||
|
53 | 38 | ) |
|
54 | 39 | ) |
|
55 | 40 | |
|
56 | class NotebookRedirectHandler(IPythonHandler): | |
|
57 | def get(self, path=''): | |
|
58 | nbm = self.notebook_manager | |
|
59 | if nbm.path_exists(path): | |
|
60 | # it's a *directory*, redirect to /tree | |
|
61 | url = url_path_join(self.base_url, 'tree', path) | |
|
62 | else: | |
|
63 | # otherwise, redirect to /files | |
|
64 | if '/files/' in path: | |
|
65 | # redirect without files/ iff it would 404 | |
|
66 | # this preserves pre-2.0-style 'files/' links | |
|
67 | # FIXME: this is hardcoded based on notebook_path, | |
|
68 | # but so is the files handler itself, | |
|
69 | # so it should work until both are cleaned up. | |
|
70 | parts = path.split('/') | |
|
71 | files_path = os.path.join(nbm.notebook_dir, *parts) | |
|
72 | if not os.path.exists(files_path): | |
|
73 | self.log.warn("Deprecated files/ URL: %s", path) | |
|
74 | path = path.replace('/files/', '/', 1) | |
|
75 | ||
|
76 | url = url_path_join(self.base_url, 'files', path) | |
|
77 | url = url_escape(url) | |
|
78 | self.log.debug("Redirecting %s to %s", self.request.path, url) | |
|
79 | self.redirect(url) | |
|
80 | 41 | |
|
81 | 42 | #----------------------------------------------------------------------------- |
|
82 | 43 | # URL to handler mappings |
@@ -85,6 +46,6 b' class NotebookRedirectHandler(IPythonHandler):' | |||
|
85 | 46 | |
|
86 | 47 | default_handlers = [ |
|
87 | 48 | (r"/notebooks%s" % notebook_path_regex, NotebookHandler), |
|
88 |
(r"/notebooks%s" % path_regex, |
|
|
49 | (r"/notebooks%s" % path_regex, FilesRedirectHandler), | |
|
89 | 50 | ] |
|
90 | 51 |
@@ -6,12 +6,14 b'' | |||
|
6 | 6 | |
|
7 | 7 | from __future__ import print_function |
|
8 | 8 | |
|
9 | import base64 | |
|
9 | 10 | import errno |
|
10 | 11 | import io |
|
11 | 12 | import json |
|
12 | 13 | import logging |
|
13 | 14 | import os |
|
14 | 15 | import random |
|
16 | import re | |
|
15 | 17 | import select |
|
16 | 18 | import signal |
|
17 | 19 | import socket |
@@ -53,8 +55,8 b' from IPython.html import DEFAULT_STATIC_FILES_PATH' | |||
|
53 | 55 | from .base.handlers import Template404 |
|
54 | 56 | from .log import log_request |
|
55 | 57 | from .services.kernels.kernelmanager import MappingKernelManager |
|
56 |
from .services. |
|
|
57 |
from .services. |
|
|
58 | from .services.contents.manager import ContentsManager | |
|
59 | from .services.contents.filemanager import FileContentsManager | |
|
58 | 60 | from .services.clusters.clustermanager import ClusterManager |
|
59 | 61 | from .services.sessions.sessionmanager import SessionManager |
|
60 | 62 | |
@@ -72,6 +74,7 b' from IPython.kernel.zmq.session import default_secure, Session' | |||
|
72 | 74 | from IPython.nbformat.sign import NotebookNotary |
|
73 | 75 | from IPython.utils.importstring import import_item |
|
74 | 76 | from IPython.utils import submodule |
|
77 | from IPython.utils.process import check_pid | |
|
75 | 78 | from IPython.utils.traitlets import ( |
|
76 | 79 | Dict, Unicode, Integer, List, Bool, Bytes, Instance, |
|
77 | 80 | DottedObjectName, TraitError, |
@@ -118,19 +121,19 b' def load_handlers(name):' | |||
|
118 | 121 | |
|
119 | 122 | class NotebookWebApplication(web.Application): |
|
120 | 123 | |
|
121 |
def __init__(self, ipython_app, kernel_manager, |
|
|
124 | def __init__(self, ipython_app, kernel_manager, contents_manager, | |
|
122 | 125 | cluster_manager, session_manager, kernel_spec_manager, log, |
|
123 | 126 | base_url, settings_overrides, jinja_env_options): |
|
124 | 127 | |
|
125 | 128 | settings = self.init_settings( |
|
126 |
ipython_app, kernel_manager, |
|
|
129 | ipython_app, kernel_manager, contents_manager, cluster_manager, | |
|
127 | 130 | session_manager, kernel_spec_manager, log, base_url, |
|
128 | 131 | settings_overrides, jinja_env_options) |
|
129 | 132 | handlers = self.init_handlers(settings) |
|
130 | 133 | |
|
131 | 134 | super(NotebookWebApplication, self).__init__(handlers, **settings) |
|
132 | 135 | |
|
133 |
def init_settings(self, ipython_app, kernel_manager, |
|
|
136 | def init_settings(self, ipython_app, kernel_manager, contents_manager, | |
|
134 | 137 | cluster_manager, session_manager, kernel_spec_manager, |
|
135 | 138 | log, base_url, settings_overrides, |
|
136 | 139 | jinja_env_options=None): |
@@ -162,13 +165,14 b' class NotebookWebApplication(web.Application):' | |||
|
162 | 165 | |
|
163 | 166 | # managers |
|
164 | 167 | kernel_manager=kernel_manager, |
|
165 |
|
|
|
168 | contents_manager=contents_manager, | |
|
166 | 169 | cluster_manager=cluster_manager, |
|
167 | 170 | session_manager=session_manager, |
|
168 | 171 | kernel_spec_manager=kernel_spec_manager, |
|
169 | 172 | |
|
170 | 173 | # IPython stuff |
|
171 | 174 | nbextensions_path = ipython_app.nbextensions_path, |
|
175 | websocket_url=ipython_app.websocket_url, | |
|
172 | 176 | mathjax_url=ipython_app.mathjax_url, |
|
173 | 177 | config=ipython_app.config, |
|
174 | 178 | jinja2_env=env, |
@@ -189,18 +193,20 b' class NotebookWebApplication(web.Application):' | |||
|
189 | 193 | handlers.extend(load_handlers('nbconvert.handlers')) |
|
190 | 194 | handlers.extend(load_handlers('kernelspecs.handlers')) |
|
191 | 195 | handlers.extend(load_handlers('services.kernels.handlers')) |
|
192 |
handlers.extend(load_handlers('services. |
|
|
196 | handlers.extend(load_handlers('services.contents.handlers')) | |
|
193 | 197 | handlers.extend(load_handlers('services.clusters.handlers')) |
|
194 | 198 | handlers.extend(load_handlers('services.sessions.handlers')) |
|
195 | 199 | handlers.extend(load_handlers('services.nbconvert.handlers')) |
|
196 | 200 | handlers.extend(load_handlers('services.kernelspecs.handlers')) |
|
197 | 201 | # FIXME: /files/ should be handled by the Contents service when it exists |
|
198 |
|
|
|
199 |
if hasattr( |
|
|
200 |
handlers. |
|
|
201 |
(r"/files/(.*)", AuthenticatedFileHandler, {'path' : |
|
|
202 | cm = settings['contents_manager'] | |
|
203 | if hasattr(cm, 'root_dir'): | |
|
204 | handlers.append( | |
|
205 | (r"/files/(.*)", AuthenticatedFileHandler, {'path' : cm.root_dir}), | |
|
206 | ) | |
|
207 | handlers.append( | |
|
202 | 208 | (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}), |
|
203 |
|
|
|
209 | ) | |
|
204 | 210 | # prepend base_url onto the patterns that we match |
|
205 | 211 | new_handlers = [] |
|
206 | 212 | for handler in handlers: |
@@ -260,9 +266,9 b" flags['no-mathjax']=(" | |||
|
260 | 266 | ) |
|
261 | 267 | |
|
262 | 268 | # Add notebook manager flags |
|
263 |
flags.update(boolean_flag('script', 'File |
|
|
264 | 'Auto-save a .py script everytime the .ipynb notebook is saved', | |
|
265 | 'Do not auto-save .py scripts for every notebook')) | |
|
269 | flags.update(boolean_flag('script', 'FileContentsManager.save_script', | |
|
270 | 'DEPRECATED, IGNORED', | |
|
271 | 'DEPRECATED, IGNORED')) | |
|
266 | 272 | |
|
267 | 273 | aliases = dict(base_aliases) |
|
268 | 274 | |
@@ -298,7 +304,7 b' class NotebookApp(BaseIPythonApplication):' | |||
|
298 | 304 | |
|
299 | 305 | classes = [ |
|
300 | 306 | KernelManager, ProfileDir, Session, MappingKernelManager, |
|
301 |
|
|
|
307 | ContentsManager, FileContentsManager, NotebookNotary, | |
|
302 | 308 | ] |
|
303 | 309 | flags = Dict(flags) |
|
304 | 310 | aliases = Dict(aliases) |
@@ -333,7 +339,33 b' class NotebookApp(BaseIPythonApplication):' | |||
|
333 | 339 | self.file_to_run = base |
|
334 | 340 | self.notebook_dir = path |
|
335 | 341 | |
|
336 |
# Network related information |
|
|
342 | # Network related information | |
|
343 | ||
|
344 | allow_origin = Unicode('', config=True, | |
|
345 | help="""Set the Access-Control-Allow-Origin header | |
|
346 | ||
|
347 | Use '*' to allow any origin to access your server. | |
|
348 | ||
|
349 | Takes precedence over allow_origin_pat. | |
|
350 | """ | |
|
351 | ) | |
|
352 | ||
|
353 | allow_origin_pat = Unicode('', config=True, | |
|
354 | help="""Use a regular expression for the Access-Control-Allow-Origin header | |
|
355 | ||
|
356 | Requests from an origin matching the expression will get replies with: | |
|
357 | ||
|
358 | Access-Control-Allow-Origin: origin | |
|
359 | ||
|
360 | where `origin` is the origin of the request. | |
|
361 | ||
|
362 | Ignored if allow_origin is set. | |
|
363 | """ | |
|
364 | ) | |
|
365 | ||
|
366 | allow_credentials = Bool(False, config=True, | |
|
367 | help="Set the Access-Control-Allow-Credentials: true header" | |
|
368 | ) | |
|
337 | 369 | |
|
338 | 370 | ip = Unicode('localhost', config=True, |
|
339 | 371 | help="The IP address the notebook server will listen on." |
@@ -357,6 +389,14 b' class NotebookApp(BaseIPythonApplication):' | |||
|
357 | 389 | help="""The full path to a private key file for usage with SSL/TLS.""" |
|
358 | 390 | ) |
|
359 | 391 | |
|
392 | cookie_secret_file = Unicode(config=True, | |
|
393 | help="""The file where the cookie secret is stored.""" | |
|
394 | ) | |
|
395 | def _cookie_secret_file_default(self): | |
|
396 | if self.profile_dir is None: | |
|
397 | return '' | |
|
398 | return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret') | |
|
399 | ||
|
360 | 400 | cookie_secret = Bytes(b'', config=True, |
|
361 | 401 | help="""The random bytes used to secure cookies. |
|
362 | 402 | By default this is a new random number every time you start the Notebook. |
@@ -367,7 +407,26 b' class NotebookApp(BaseIPythonApplication):' | |||
|
367 | 407 | """ |
|
368 | 408 | ) |
|
369 | 409 | def _cookie_secret_default(self): |
|
370 | return os.urandom(1024) | |
|
410 | if os.path.exists(self.cookie_secret_file): | |
|
411 | with io.open(self.cookie_secret_file, 'rb') as f: | |
|
412 | return f.read() | |
|
413 | else: | |
|
414 | secret = base64.encodestring(os.urandom(1024)) | |
|
415 | self._write_cookie_secret_file(secret) | |
|
416 | return secret | |
|
417 | ||
|
418 | def _write_cookie_secret_file(self, secret): | |
|
419 | """write my secret to my secret_file""" | |
|
420 | self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file) | |
|
421 | with io.open(self.cookie_secret_file, 'wb') as f: | |
|
422 | f.write(secret) | |
|
423 | try: | |
|
424 | os.chmod(self.cookie_secret_file, 0o600) | |
|
425 | except OSError: | |
|
426 | self.log.warn( | |
|
427 | "Could not set permissions on %s", | |
|
428 | self.cookie_secret_file | |
|
429 | ) | |
|
371 | 430 | |
|
372 | 431 | password = Unicode(u'', config=True, |
|
373 | 432 | help="""Hashed password to use for web authentication. |
@@ -456,6 +515,13 b' class NotebookApp(BaseIPythonApplication):' | |||
|
456 | 515 | def _nbextensions_path_default(self): |
|
457 | 516 | return [os.path.join(get_ipython_dir(), 'nbextensions')] |
|
458 | 517 | |
|
518 | websocket_url = Unicode("", config=True, | |
|
519 | help="""The base URL for websockets, | |
|
520 | if it differs from the HTTP server (hint: it almost certainly doesn't). | |
|
521 | ||
|
522 | Should be in the form of an HTTP origin: ws[s]://hostname[:port] | |
|
523 | """ | |
|
524 | ) | |
|
459 | 525 | mathjax_url = Unicode("", config=True, |
|
460 | 526 | help="""The url for MathJax.js.""" |
|
461 | 527 | ) |
@@ -482,13 +548,7 b' class NotebookApp(BaseIPythonApplication):' | |||
|
482 | 548 | return url |
|
483 | 549 | |
|
484 | 550 | # no local mathjax, serve from CDN |
|
485 | if self.certfile: | |
|
486 | # HTTPS: load from Rackspace CDN, because SSL certificate requires it | |
|
487 | host = u"https://c328740.ssl.cf1.rackcdn.com" | |
|
488 | else: | |
|
489 | host = u"http://cdn.mathjax.org" | |
|
490 | ||
|
491 | url = host + u"/mathjax/latest/MathJax.js" | |
|
551 | url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js" | |
|
492 | 552 | self.log.info("Using MathJax from CDN: %s", url) |
|
493 | 553 | return url |
|
494 | 554 | |
@@ -499,7 +559,7 b' class NotebookApp(BaseIPythonApplication):' | |||
|
499 | 559 | else: |
|
500 | 560 | self.log.info("Using MathJax: %s", new) |
|
501 | 561 | |
|
502 |
|
|
|
562 | contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager', | |
|
503 | 563 | config=True, |
|
504 | 564 | help='The notebook manager class to use.' |
|
505 | 565 | ) |
@@ -563,7 +623,7 b' class NotebookApp(BaseIPythonApplication):' | |||
|
563 | 623 | raise TraitError("No such notebook dir: %r" % new) |
|
564 | 624 | |
|
565 | 625 | # setting App.notebook_dir implies setting notebook and kernel dirs as well |
|
566 |
self.config.File |
|
|
626 | self.config.FileContentsManager.root_dir = new | |
|
567 | 627 | self.config.MappingKernelManager.root_dir = new |
|
568 | 628 | |
|
569 | 629 | |
@@ -589,11 +649,8 b' class NotebookApp(BaseIPythonApplication):' | |||
|
589 | 649 | |
|
590 | 650 | def init_kernel_argv(self): |
|
591 | 651 | """construct the kernel arguments""" |
|
592 | self.kernel_argv = [] | |
|
593 | # Kernel should inherit default config file from frontend | |
|
594 | self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name) | |
|
595 | 652 | # Kernel should get *absolute* path to profile directory |
|
596 |
self.kernel_argv |
|
|
653 | self.kernel_argv = ["--profile-dir", self.profile_dir.location] | |
|
597 | 654 | |
|
598 | 655 | def init_configurables(self): |
|
599 | 656 | # force Session default to be secure |
@@ -603,10 +660,12 b' class NotebookApp(BaseIPythonApplication):' | |||
|
603 | 660 | parent=self, log=self.log, kernel_argv=self.kernel_argv, |
|
604 | 661 | connection_dir = self.profile_dir.security_dir, |
|
605 | 662 | ) |
|
606 |
kls = import_item(self. |
|
|
607 |
self. |
|
|
663 | kls = import_item(self.contents_manager_class) | |
|
664 | self.contents_manager = kls(parent=self, log=self.log) | |
|
608 | 665 | kls = import_item(self.session_manager_class) |
|
609 |
self.session_manager = kls(parent=self, log=self.log |
|
|
666 | self.session_manager = kls(parent=self, log=self.log, | |
|
667 | kernel_manager=self.kernel_manager, | |
|
668 | contents_manager=self.contents_manager) | |
|
610 | 669 | kls = import_item(self.cluster_manager_class) |
|
611 | 670 | self.cluster_manager = kls(parent=self, log=self.log) |
|
612 | 671 | self.cluster_manager.update_profiles() |
@@ -625,8 +684,13 b' class NotebookApp(BaseIPythonApplication):' | |||
|
625 | 684 | |
|
626 | 685 | def init_webapp(self): |
|
627 | 686 | """initialize tornado webapp and httpserver""" |
|
687 | self.webapp_settings['allow_origin'] = self.allow_origin | |
|
688 | if self.allow_origin_pat: | |
|
689 | self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat) | |
|
690 | self.webapp_settings['allow_credentials'] = self.allow_credentials | |
|
691 | ||
|
628 | 692 | self.web_app = NotebookWebApplication( |
|
629 |
self, self.kernel_manager, self. |
|
|
693 | self, self.kernel_manager, self.contents_manager, | |
|
630 | 694 | self.cluster_manager, self.session_manager, self.kernel_spec_manager, |
|
631 | 695 | self.log, self.base_url, self.webapp_settings, |
|
632 | 696 | self.jinja_environment_options |
@@ -717,8 +781,6 b' class NotebookApp(BaseIPythonApplication):' | |||
|
717 | 781 | |
|
718 | 782 | This doesn't work on Windows. |
|
719 | 783 | """ |
|
720 | # FIXME: remove this delay when pyzmq dependency is >= 2.1.11 | |
|
721 | time.sleep(0.1) | |
|
722 | 784 | info = self.log.info |
|
723 | 785 | info('interrupted') |
|
724 | 786 | print(self.notebook_info()) |
@@ -778,7 +840,7 b' class NotebookApp(BaseIPythonApplication):' | |||
|
778 | 840 | |
|
779 | 841 | def notebook_info(self): |
|
780 | 842 | "Return the current working directory and the server url information" |
|
781 |
info = self. |
|
|
843 | info = self.contents_manager.info_string() + "\n" | |
|
782 | 844 | info += "%d active kernels \n" % len(self.kernel_manager._kernels) |
|
783 | 845 | return info + "The IPython Notebook is running at: %s" % self.display_url |
|
784 | 846 | |
@@ -790,6 +852,7 b' class NotebookApp(BaseIPythonApplication):' | |||
|
790 | 852 | 'secure': bool(self.certfile), |
|
791 | 853 | 'base_url': self.base_url, |
|
792 | 854 | 'notebook_dir': os.path.abspath(self.notebook_dir), |
|
855 | 'pid': os.getpid() | |
|
793 | 856 | } |
|
794 | 857 | |
|
795 | 858 | def write_server_info_file(self): |
@@ -863,8 +926,17 b" def list_running_servers(profile='default'):" | |||
|
863 | 926 | for file in os.listdir(pd.security_dir): |
|
864 | 927 | if file.startswith('nbserver-'): |
|
865 | 928 | with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f: |
|
866 |
|
|
|
929 | info = json.load(f) | |
|
867 | 930 | |
|
931 | # Simple check whether that process is really still running | |
|
932 | if check_pid(info['pid']): | |
|
933 | yield info | |
|
934 | else: | |
|
935 | # If the process has died, try to delete its info file | |
|
936 | try: | |
|
937 | os.unlink(file) | |
|
938 | except OSError: | |
|
939 | pass # TODO: This should warn or log or something | |
|
868 | 940 | #----------------------------------------------------------------------------- |
|
869 | 941 | # Main entry point |
|
870 | 942 | #----------------------------------------------------------------------------- |
@@ -21,7 +21,6 b' from zmq.eventloop import ioloop' | |||
|
21 | 21 | |
|
22 | 22 | from IPython.config.configurable import LoggingConfigurable |
|
23 | 23 | from IPython.utils.traitlets import Dict, Instance, CFloat |
|
24 | from IPython.parallel.apps.ipclusterapp import IPClusterStart | |
|
25 | 24 | from IPython.core.profileapp import list_profiles_in |
|
26 | 25 | from IPython.core.profiledir import ProfileDir |
|
27 | 26 | from IPython.utils import py3compat |
@@ -33,17 +32,6 b' from IPython.utils.path import get_ipython_dir' | |||
|
33 | 32 | #----------------------------------------------------------------------------- |
|
34 | 33 | |
|
35 | 34 | |
|
36 | class DummyIPClusterStart(IPClusterStart): | |
|
37 | """Dummy subclass to skip init steps that conflict with global app. | |
|
38 | ||
|
39 | Instantiating and initializing this class should result in fully configured | |
|
40 | launchers, but no other side effects or state. | |
|
41 | """ | |
|
42 | ||
|
43 | def init_signal(self): | |
|
44 | pass | |
|
45 | def reinit_logging(self): | |
|
46 | pass | |
|
47 | 35 | |
|
48 | 36 | |
|
49 | 37 | class ClusterManager(LoggingConfigurable): |
@@ -59,6 +47,20 b' class ClusterManager(LoggingConfigurable):' | |||
|
59 | 47 | return IOLoop.instance() |
|
60 | 48 | |
|
61 | 49 | def build_launchers(self, profile_dir): |
|
50 | from IPython.parallel.apps.ipclusterapp import IPClusterStart | |
|
51 | ||
|
52 | class DummyIPClusterStart(IPClusterStart): | |
|
53 | """Dummy subclass to skip init steps that conflict with global app. | |
|
54 | ||
|
55 | Instantiating and initializing this class should result in fully configured | |
|
56 | launchers, but no other side effects or state. | |
|
57 | """ | |
|
58 | ||
|
59 | def init_signal(self): | |
|
60 | pass | |
|
61 | def reinit_logging(self): | |
|
62 | pass | |
|
63 | ||
|
62 | 64 | starter = DummyIPClusterStart(log=self.log) |
|
63 | 65 | starter.initialize(['--profile-dir', profile_dir]) |
|
64 | 66 | cl = starter.controller_launcher |
|
1 | NO CONTENT: file renamed from IPython/html/services/notebooks/__init__.py to IPython/html/services/contents/__init__.py |
|
1 | NO CONTENT: file renamed from IPython/html/services/notebooks/tests/__init__.py to IPython/html/services/contents/tests/__init__.py |
@@ -1,6 +1,7 b'' | |||
|
1 | 1 | # coding: utf-8 |
|
2 |
"""Test the |
|
|
2 | """Test the contents webservice API.""" | |
|
3 | 3 | |
|
4 | import base64 | |
|
4 | 5 | import io |
|
5 | 6 | import json |
|
6 | 7 | import os |
@@ -21,23 +22,21 b' from IPython.utils import py3compat' | |||
|
21 | 22 | from IPython.utils.data import uniq_stable |
|
22 | 23 | |
|
23 | 24 | |
|
24 | # TODO: Remove this after we create the contents web service and directories are | |
|
25 | # no longer listed by the notebook web service. | |
|
26 | def notebooks_only(nb_list): | |
|
27 | return [nb for nb in nb_list if nb['type']=='notebook'] | |
|
25 | def notebooks_only(dir_model): | |
|
26 | return [nb for nb in dir_model['content'] if nb['type']=='notebook'] | |
|
28 | 27 | |
|
29 |
def dirs_only( |
|
|
30 |
return [x for x in |
|
|
28 | def dirs_only(dir_model): | |
|
29 | return [x for x in dir_model['content'] if x['type']=='directory'] | |
|
31 | 30 | |
|
32 | 31 | |
|
33 |
class |
|
|
34 |
"""Wrapper for |
|
|
32 | class API(object): | |
|
33 | """Wrapper for contents API calls.""" | |
|
35 | 34 | def __init__(self, base_url): |
|
36 | 35 | self.base_url = base_url |
|
37 | 36 | |
|
38 | 37 | def _req(self, verb, path, body=None): |
|
39 | 38 | response = requests.request(verb, |
|
40 |
url_path_join(self.base_url, 'api/ |
|
|
39 | url_path_join(self.base_url, 'api/contents', path), | |
|
41 | 40 | data=body, |
|
42 | 41 | ) |
|
43 | 42 | response.raise_for_status() |
@@ -49,8 +48,11 b' class NBAPI(object):' | |||
|
49 | 48 | def read(self, name, path='/'): |
|
50 | 49 | return self._req('GET', url_path_join(path, name)) |
|
51 | 50 | |
|
52 | def create_untitled(self, path='/'): | |
|
53 | return self._req('POST', path) | |
|
51 | def create_untitled(self, path='/', ext=None): | |
|
52 | body = None | |
|
53 | if ext: | |
|
54 | body = json.dumps({'ext': ext}) | |
|
55 | return self._req('POST', path, body) | |
|
54 | 56 | |
|
55 | 57 | def upload_untitled(self, body, path='/'): |
|
56 | 58 | return self._req('POST', path, body) |
@@ -65,6 +67,9 b' class NBAPI(object):' | |||
|
65 | 67 | def upload(self, name, body, path='/'): |
|
66 | 68 | return self._req('PUT', url_path_join(path, name), body) |
|
67 | 69 | |
|
70 | def mkdir(self, name, path='/'): | |
|
71 | return self._req('PUT', url_path_join(path, name), json.dumps({'type': 'directory'})) | |
|
72 | ||
|
68 | 73 | def copy(self, copy_from, copy_to, path='/'): |
|
69 | 74 | body = json.dumps({'copy_from':copy_from}) |
|
70 | 75 | return self._req('PUT', url_path_join(path, copy_to), body) |
@@ -112,8 +117,20 b' class APITest(NotebookTestBase):' | |||
|
112 | 117 | del dirs[0] # remove '' |
|
113 | 118 | top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs} |
|
114 | 119 | |
|
120 | @staticmethod | |
|
121 | def _blob_for_name(name): | |
|
122 | return name.encode('utf-8') + b'\xFF' | |
|
123 | ||
|
124 | @staticmethod | |
|
125 | def _txt_for_name(name): | |
|
126 | return u'%s text file' % name | |
|
127 | ||
|
115 | 128 | def setUp(self): |
|
116 | 129 | nbdir = self.notebook_dir.name |
|
130 | self.blob = os.urandom(100) | |
|
131 | self.b64_blob = base64.encodestring(self.blob).decode('ascii') | |
|
132 | ||
|
133 | ||
|
117 | 134 | |
|
118 | 135 | for d in (self.dirs + self.hidden_dirs): |
|
119 | 136 | d.replace('/', os.sep) |
@@ -122,12 +139,22 b' class APITest(NotebookTestBase):' | |||
|
122 | 139 | |
|
123 | 140 | for d, name in self.dirs_nbs: |
|
124 | 141 | d = d.replace('/', os.sep) |
|
142 | # create a notebook | |
|
125 | 143 | with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w', |
|
126 | 144 | encoding='utf-8') as f: |
|
127 | 145 | nb = new_notebook(name=name) |
|
128 | 146 | write(nb, f, format='ipynb') |
|
129 | 147 | |
|
130 | self.nb_api = NBAPI(self.base_url()) | |
|
148 | # create a text file | |
|
149 | with io.open(pjoin(nbdir, d, '%s.txt' % name), 'w', | |
|
150 | encoding='utf-8') as f: | |
|
151 | f.write(self._txt_for_name(name)) | |
|
152 | ||
|
153 | # create a binary file | |
|
154 | with io.open(pjoin(nbdir, d, '%s.blob' % name), 'wb') as f: | |
|
155 | f.write(self._blob_for_name(name)) | |
|
156 | ||
|
157 | self.api = API(self.base_url()) | |
|
131 | 158 | |
|
132 | 159 | def tearDown(self): |
|
133 | 160 | nbdir = self.notebook_dir.name |
@@ -139,175 +166,287 b' class APITest(NotebookTestBase):' | |||
|
139 | 166 | os.unlink(pjoin(nbdir, 'inroot.ipynb')) |
|
140 | 167 | |
|
141 | 168 | def test_list_notebooks(self): |
|
142 |
nbs = notebooks_only(self. |
|
|
169 | nbs = notebooks_only(self.api.list().json()) | |
|
143 | 170 | self.assertEqual(len(nbs), 1) |
|
144 | 171 | self.assertEqual(nbs[0]['name'], 'inroot.ipynb') |
|
145 | 172 | |
|
146 |
nbs = notebooks_only(self. |
|
|
173 | nbs = notebooks_only(self.api.list('/Directory with spaces in/').json()) | |
|
147 | 174 | self.assertEqual(len(nbs), 1) |
|
148 | 175 | self.assertEqual(nbs[0]['name'], 'inspace.ipynb') |
|
149 | 176 | |
|
150 |
nbs = notebooks_only(self. |
|
|
177 | nbs = notebooks_only(self.api.list(u'/unicodé/').json()) | |
|
151 | 178 | self.assertEqual(len(nbs), 1) |
|
152 | 179 | self.assertEqual(nbs[0]['name'], 'innonascii.ipynb') |
|
153 | 180 | self.assertEqual(nbs[0]['path'], u'unicodé') |
|
154 | 181 | |
|
155 |
nbs = notebooks_only(self. |
|
|
182 | nbs = notebooks_only(self.api.list('/foo/bar/').json()) | |
|
156 | 183 | self.assertEqual(len(nbs), 1) |
|
157 | 184 | self.assertEqual(nbs[0]['name'], 'baz.ipynb') |
|
158 | 185 | self.assertEqual(nbs[0]['path'], 'foo/bar') |
|
159 | 186 | |
|
160 |
nbs = notebooks_only(self. |
|
|
187 | nbs = notebooks_only(self.api.list('foo').json()) | |
|
161 | 188 | self.assertEqual(len(nbs), 4) |
|
162 | 189 | nbnames = { normalize('NFC', n['name']) for n in nbs } |
|
163 | 190 | expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb'] |
|
164 | 191 | expected = { normalize('NFC', name) for name in expected } |
|
165 | 192 | self.assertEqual(nbnames, expected) |
|
166 | 193 | |
|
167 |
nbs = notebooks_only(self. |
|
|
194 | nbs = notebooks_only(self.api.list('ordering').json()) | |
|
168 | 195 | nbnames = [n['name'] for n in nbs] |
|
169 | 196 | expected = ['A.ipynb', 'b.ipynb', 'C.ipynb'] |
|
170 | 197 | self.assertEqual(nbnames, expected) |
|
171 | 198 | |
|
172 | 199 | def test_list_dirs(self): |
|
173 |
dirs = dirs_only(self. |
|
|
200 | dirs = dirs_only(self.api.list().json()) | |
|
174 | 201 | dir_names = {normalize('NFC', d['name']) for d in dirs} |
|
175 | 202 | self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs |
|
176 | 203 | |
|
177 | 204 | def test_list_nonexistant_dir(self): |
|
178 | 205 | with assert_http_error(404): |
|
179 |
self. |
|
|
206 | self.api.list('nonexistant') | |
|
180 | 207 | |
|
181 | def test_get_contents(self): | |
|
208 | def test_get_nb_contents(self): | |
|
182 | 209 | for d, name in self.dirs_nbs: |
|
183 |
nb = self. |
|
|
210 | nb = self.api.read('%s.ipynb' % name, d+'/').json() | |
|
184 | 211 | self.assertEqual(nb['name'], u'%s.ipynb' % name) |
|
212 | self.assertEqual(nb['type'], 'notebook') | |
|
213 | self.assertIn('content', nb) | |
|
214 | self.assertEqual(nb['format'], 'json') | |
|
185 | 215 | self.assertIn('content', nb) |
|
186 | 216 | self.assertIn('metadata', nb['content']) |
|
187 | 217 | self.assertIsInstance(nb['content']['metadata'], dict) |
|
188 | 218 | |
|
219 | def test_get_contents_no_such_file(self): | |
|
189 | 220 | # Name that doesn't exist - should be a 404 |
|
190 | 221 | with assert_http_error(404): |
|
191 |
self. |
|
|
222 | self.api.read('q.ipynb', 'foo') | |
|
192 | 223 | |
|
193 | def _check_nb_created(self, resp, name, path): | |
|
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 | ||
|
247 | # Name that doesn't exist - should be a 404 | |
|
248 | with assert_http_error(404): | |
|
249 | self.api.read('q.txt', 'foo') | |
|
250 | ||
|
251 | def _check_created(self, resp, name, path, type='notebook'): | |
|
194 | 252 | self.assertEqual(resp.status_code, 201) |
|
195 | 253 | location_header = py3compat.str_to_unicode(resp.headers['Location']) |
|
196 |
self.assertEqual(location_header, url_escape(url_path_join(u'/api/ |
|
|
197 | self.assertEqual(resp.json()['name'], name) | |
|
198 | assert os.path.isfile(pjoin( | |
|
254 | self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path, name))) | |
|
255 | rjson = resp.json() | |
|
256 | self.assertEqual(rjson['name'], name) | |
|
257 | self.assertEqual(rjson['path'], path) | |
|
258 | self.assertEqual(rjson['type'], type) | |
|
259 | isright = os.path.isdir if type == 'directory' else os.path.isfile | |
|
260 | assert isright(pjoin( | |
|
199 | 261 | self.notebook_dir.name, |
|
200 | 262 | path.replace('/', os.sep), |
|
201 | 263 | name, |
|
202 | 264 | )) |
|
203 | 265 | |
|
204 | 266 | def test_create_untitled(self): |
|
205 |
resp = self. |
|
|
206 |
self._check_ |
|
|
267 | resp = self.api.create_untitled(path=u'å b') | |
|
268 | self._check_created(resp, 'Untitled0.ipynb', u'å b') | |
|
207 | 269 | |
|
208 | 270 | # Second time |
|
209 |
resp = self. |
|
|
210 |
self._check_ |
|
|
271 | resp = self.api.create_untitled(path=u'å b') | |
|
272 | self._check_created(resp, 'Untitled1.ipynb', u'å b') | |
|
211 | 273 | |
|
212 | 274 | # And two directories down |
|
213 |
resp = self. |
|
|
214 |
self._check_ |
|
|
275 | resp = self.api.create_untitled(path='foo/bar') | |
|
276 | self._check_created(resp, 'Untitled0.ipynb', 'foo/bar') | |
|
277 | ||
|
278 | def test_create_untitled_txt(self): | |
|
279 | resp = self.api.create_untitled(path='foo/bar', ext='.txt') | |
|
280 | self._check_created(resp, 'untitled0.txt', 'foo/bar', type='file') | |
|
281 | ||
|
282 | resp = self.api.read(path='foo/bar', name='untitled0.txt') | |
|
283 | model = resp.json() | |
|
284 | self.assertEqual(model['type'], 'file') | |
|
285 | self.assertEqual(model['format'], 'text') | |
|
286 | self.assertEqual(model['content'], '') | |
|
215 | 287 | |
|
216 | 288 | def test_upload_untitled(self): |
|
217 | 289 | nb = new_notebook(name='Upload test') |
|
218 | nbmodel = {'content': nb} | |
|
219 |
resp = self. |
|
|
290 | nbmodel = {'content': nb, 'type': 'notebook'} | |
|
291 | resp = self.api.upload_untitled(path=u'å b', | |
|
220 | 292 | body=json.dumps(nbmodel)) |
|
221 |
self._check_ |
|
|
293 | self._check_created(resp, 'Untitled0.ipynb', u'å b') | |
|
222 | 294 | |
|
223 | 295 | def test_upload(self): |
|
224 | 296 | nb = new_notebook(name=u'ignored') |
|
225 | nbmodel = {'content': nb} | |
|
226 |
resp = self. |
|
|
297 | nbmodel = {'content': nb, 'type': 'notebook'} | |
|
298 | resp = self.api.upload(u'Upload tést.ipynb', path=u'å b', | |
|
227 | 299 | body=json.dumps(nbmodel)) |
|
228 |
self._check_ |
|
|
300 | self._check_created(resp, u'Upload tést.ipynb', u'å b') | |
|
301 | ||
|
302 | def test_mkdir(self): | |
|
303 | resp = self.api.mkdir(u'New ∂ir', path=u'å b') | |
|
304 | self._check_created(resp, u'New ∂ir', u'å b', type='directory') | |
|
305 | ||
|
306 | def test_mkdir_hidden_400(self): | |
|
307 | with assert_http_error(400): | |
|
308 | resp = self.api.mkdir(u'.hidden', path=u'å b') | |
|
309 | ||
|
310 | def test_upload_txt(self): | |
|
311 | body = u'ünicode téxt' | |
|
312 | model = { | |
|
313 | 'content' : body, | |
|
314 | 'format' : 'text', | |
|
315 | 'type' : 'file', | |
|
316 | } | |
|
317 | resp = self.api.upload(u'Upload tést.txt', path=u'å b', | |
|
318 | body=json.dumps(model)) | |
|
319 | ||
|
320 | # check roundtrip | |
|
321 | resp = self.api.read(path=u'å b', name=u'Upload tést.txt') | |
|
322 | model = resp.json() | |
|
323 | self.assertEqual(model['type'], 'file') | |
|
324 | self.assertEqual(model['format'], 'text') | |
|
325 | self.assertEqual(model['content'], body) | |
|
326 | ||
|
327 | def test_upload_b64(self): | |
|
328 | body = b'\xFFblob' | |
|
329 | b64body = base64.encodestring(body).decode('ascii') | |
|
330 | model = { | |
|
331 | 'content' : b64body, | |
|
332 | 'format' : 'base64', | |
|
333 | 'type' : 'file', | |
|
334 | } | |
|
335 | resp = self.api.upload(u'Upload tést.blob', path=u'å b', | |
|
336 | body=json.dumps(model)) | |
|
337 | ||
|
338 | # check roundtrip | |
|
339 | resp = self.api.read(path=u'å b', name=u'Upload tést.blob') | |
|
340 | model = resp.json() | |
|
341 | self.assertEqual(model['type'], 'file') | |
|
342 | self.assertEqual(model['format'], 'base64') | |
|
343 | decoded = base64.decodestring(model['content'].encode('ascii')) | |
|
344 | self.assertEqual(decoded, body) | |
|
229 | 345 | |
|
230 | 346 | def test_upload_v2(self): |
|
231 | 347 | nb = v2.new_notebook() |
|
232 | 348 | ws = v2.new_worksheet() |
|
233 | 349 | nb.worksheets.append(ws) |
|
234 | 350 | ws.cells.append(v2.new_code_cell(input='print("hi")')) |
|
235 | nbmodel = {'content': nb} | |
|
236 |
resp = self. |
|
|
351 | nbmodel = {'content': nb, 'type': 'notebook'} | |
|
352 | resp = self.api.upload(u'Upload tést.ipynb', path=u'å b', | |
|
237 | 353 | body=json.dumps(nbmodel)) |
|
238 |
self._check_ |
|
|
239 |
resp = self. |
|
|
354 | self._check_created(resp, u'Upload tést.ipynb', u'å b') | |
|
355 | resp = self.api.read(u'Upload tést.ipynb', u'å b') | |
|
240 | 356 | data = resp.json() |
|
241 | 357 | self.assertEqual(data['content']['nbformat'], current.nbformat) |
|
242 | 358 | self.assertEqual(data['content']['orig_nbformat'], 2) |
|
243 | 359 | |
|
244 | 360 | def test_copy_untitled(self): |
|
245 |
resp = self. |
|
|
246 |
self._check_ |
|
|
361 | resp = self.api.copy_untitled(u'ç d.ipynb', path=u'å b') | |
|
362 | self._check_created(resp, u'ç d-Copy0.ipynb', u'å b') | |
|
247 | 363 | |
|
248 | 364 | def test_copy(self): |
|
249 |
resp = self. |
|
|
250 |
self._check_ |
|
|
365 | resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b') | |
|
366 | self._check_created(resp, u'cøpy.ipynb', u'å b') | |
|
367 | ||
|
368 | def test_copy_path(self): | |
|
369 | resp = self.api.copy(u'foo/a.ipynb', u'cøpyfoo.ipynb', path=u'å b') | |
|
370 | self._check_created(resp, u'cøpyfoo.ipynb', u'å b') | |
|
371 | ||
|
372 | def test_copy_dir_400(self): | |
|
373 | # can't copy directories | |
|
374 | with assert_http_error(400): | |
|
375 | resp = self.api.copy(u'å b', u'å c') | |
|
251 | 376 | |
|
252 | 377 | def test_delete(self): |
|
253 | 378 | for d, name in self.dirs_nbs: |
|
254 |
resp = self. |
|
|
379 | resp = self.api.delete('%s.ipynb' % name, d) | |
|
255 | 380 | self.assertEqual(resp.status_code, 204) |
|
256 | 381 | |
|
257 | 382 | for d in self.dirs + ['/']: |
|
258 |
nbs = notebooks_only(self. |
|
|
383 | nbs = notebooks_only(self.api.list(d).json()) | |
|
259 | 384 | self.assertEqual(len(nbs), 0) |
|
260 | 385 | |
|
386 | def test_delete_dirs(self): | |
|
387 | # depth-first delete everything, so we don't try to delete empty directories | |
|
388 | for name in sorted(self.dirs + ['/'], key=len, reverse=True): | |
|
389 | listing = self.api.list(name).json()['content'] | |
|
390 | for model in listing: | |
|
391 | self.api.delete(model['name'], model['path']) | |
|
392 | listing = self.api.list('/').json()['content'] | |
|
393 | self.assertEqual(listing, []) | |
|
394 | ||
|
395 | def test_delete_non_empty_dir(self): | |
|
396 | """delete non-empty dir raises 400""" | |
|
397 | with assert_http_error(400): | |
|
398 | self.api.delete(u'å b') | |
|
399 | ||
|
261 | 400 | def test_rename(self): |
|
262 |
resp = self. |
|
|
401 | resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb') | |
|
263 | 402 | self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb') |
|
264 | 403 | self.assertEqual(resp.json()['name'], 'z.ipynb') |
|
265 | 404 | assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb')) |
|
266 | 405 | |
|
267 |
nbs = notebooks_only(self. |
|
|
406 | nbs = notebooks_only(self.api.list('foo').json()) | |
|
268 | 407 | nbnames = set(n['name'] for n in nbs) |
|
269 | 408 | self.assertIn('z.ipynb', nbnames) |
|
270 | 409 | self.assertNotIn('a.ipynb', nbnames) |
|
271 | 410 | |
|
272 | 411 | def test_rename_existing(self): |
|
273 | 412 | with assert_http_error(409): |
|
274 |
self. |
|
|
413 | self.api.rename('a.ipynb', 'foo', 'b.ipynb') | |
|
275 | 414 | |
|
276 | 415 | def test_save(self): |
|
277 |
resp = self. |
|
|
416 | resp = self.api.read('a.ipynb', 'foo') | |
|
278 | 417 | nbcontent = json.loads(resp.text)['content'] |
|
279 | 418 | nb = to_notebook_json(nbcontent) |
|
280 | 419 | ws = new_worksheet() |
|
281 | 420 | nb.worksheets = [ws] |
|
282 | 421 | ws.cells.append(new_heading_cell(u'Created by test ³')) |
|
283 | 422 | |
|
284 | nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb} | |
|
285 |
resp = self. |
|
|
423 | nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'} | |
|
424 | resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel)) | |
|
286 | 425 | |
|
287 | 426 | nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb') |
|
288 | 427 | with io.open(nbfile, 'r', encoding='utf-8') as f: |
|
289 | 428 | newnb = read(f, format='ipynb') |
|
290 | 429 | self.assertEqual(newnb.worksheets[0].cells[0].source, |
|
291 | 430 | u'Created by test ³') |
|
292 |
nbcontent = self. |
|
|
431 | nbcontent = self.api.read('a.ipynb', 'foo').json()['content'] | |
|
293 | 432 | newnb = to_notebook_json(nbcontent) |
|
294 | 433 | self.assertEqual(newnb.worksheets[0].cells[0].source, |
|
295 | 434 | u'Created by test ³') |
|
296 | 435 | |
|
297 | 436 | # Save and rename |
|
298 | nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb} | |
|
299 |
resp = self. |
|
|
437 | nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb, 'type': 'notebook'} | |
|
438 | resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel)) | |
|
300 | 439 | saved = resp.json() |
|
301 | 440 | self.assertEqual(saved['name'], 'a2.ipynb') |
|
302 | 441 | self.assertEqual(saved['path'], 'foo/bar') |
|
303 | 442 | assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb')) |
|
304 | 443 | assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')) |
|
305 | 444 | with assert_http_error(404): |
|
306 |
self. |
|
|
445 | self.api.read('a.ipynb', 'foo') | |
|
307 | 446 | |
|
308 | 447 | def test_checkpoints(self): |
|
309 |
resp = self. |
|
|
310 |
r = self. |
|
|
448 | resp = self.api.read('a.ipynb', 'foo') | |
|
449 | r = self.api.new_checkpoint('a.ipynb', 'foo') | |
|
311 | 450 | self.assertEqual(r.status_code, 201) |
|
312 | 451 | cp1 = r.json() |
|
313 | 452 | self.assertEqual(set(cp1), {'id', 'last_modified'}) |
@@ -321,27 +460,26 b' class APITest(NotebookTestBase):' | |||
|
321 | 460 | hcell = new_heading_cell('Created by test') |
|
322 | 461 | ws.cells.append(hcell) |
|
323 | 462 | # Save |
|
324 | nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb} | |
|
325 |
resp = self. |
|
|
463 | nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'} | |
|
464 | resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel)) | |
|
326 | 465 | |
|
327 | 466 | # List checkpoints |
|
328 |
cps = self. |
|
|
467 | cps = self.api.get_checkpoints('a.ipynb', 'foo').json() | |
|
329 | 468 | self.assertEqual(cps, [cp1]) |
|
330 | 469 | |
|
331 |
nbcontent = self. |
|
|
470 | nbcontent = self.api.read('a.ipynb', 'foo').json()['content'] | |
|
332 | 471 | nb = to_notebook_json(nbcontent) |
|
333 | 472 | self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test') |
|
334 | 473 | |
|
335 | 474 | # Restore cp1 |
|
336 |
r = self. |
|
|
475 | r = self.api.restore_checkpoint('a.ipynb', 'foo', cp1['id']) | |
|
337 | 476 | self.assertEqual(r.status_code, 204) |
|
338 |
nbcontent = self. |
|
|
477 | nbcontent = self.api.read('a.ipynb', 'foo').json()['content'] | |
|
339 | 478 | nb = to_notebook_json(nbcontent) |
|
340 | 479 | self.assertEqual(nb.worksheets, []) |
|
341 | 480 | |
|
342 | 481 | # Delete cp1 |
|
343 |
r = self. |
|
|
482 | r = self.api.delete_checkpoint('a.ipynb', 'foo', cp1['id']) | |
|
344 | 483 | self.assertEqual(r.status_code, 204) |
|
345 |
cps = self. |
|
|
484 | cps = self.api.get_checkpoints('a.ipynb', 'foo').json() | |
|
346 | 485 | self.assertEqual(cps, []) |
|
347 |
@@ -15,68 +15,68 b' from IPython.utils.tempdir import TemporaryDirectory' | |||
|
15 | 15 | from IPython.utils.traitlets import TraitError |
|
16 | 16 | from IPython.html.utils import url_path_join |
|
17 | 17 | |
|
18 |
from ..file |
|
|
19 |
from .. |
|
|
18 | from ..filemanager import FileContentsManager | |
|
19 | from ..manager import ContentsManager | |
|
20 | 20 | |
|
21 | 21 | |
|
22 |
class TestFile |
|
|
22 | class TestFileContentsManager(TestCase): | |
|
23 | 23 | |
|
24 |
def test_ |
|
|
24 | def test_root_dir(self): | |
|
25 | 25 | with TemporaryDirectory() as td: |
|
26 |
fm = File |
|
|
27 |
self.assertEqual(fm. |
|
|
26 | fm = FileContentsManager(root_dir=td) | |
|
27 | self.assertEqual(fm.root_dir, td) | |
|
28 | 28 | |
|
29 |
def test_missing_ |
|
|
29 | def test_missing_root_dir(self): | |
|
30 | 30 | with TemporaryDirectory() as td: |
|
31 |
|
|
|
32 |
self.assertRaises(TraitError, File |
|
|
31 | root = os.path.join(td, 'notebook', 'dir', 'is', 'missing') | |
|
32 | self.assertRaises(TraitError, FileContentsManager, root_dir=root) | |
|
33 | 33 | |
|
34 |
def test_invalid_ |
|
|
34 | def test_invalid_root_dir(self): | |
|
35 | 35 | with NamedTemporaryFile() as tf: |
|
36 |
self.assertRaises(TraitError, File |
|
|
36 | self.assertRaises(TraitError, FileContentsManager, root_dir=tf.name) | |
|
37 | 37 | |
|
38 | 38 | def test_get_os_path(self): |
|
39 | 39 | # full filesystem path should be returned with correct operating system |
|
40 | 40 | # separators. |
|
41 | 41 | with TemporaryDirectory() as td: |
|
42 |
|
|
|
43 |
fm = File |
|
|
42 | root = td | |
|
43 | fm = FileContentsManager(root_dir=root) | |
|
44 | 44 | path = fm._get_os_path('test.ipynb', '/path/to/notebook/') |
|
45 | 45 | rel_path_list = '/path/to/notebook/test.ipynb'.split('/') |
|
46 |
fs_path = os.path.join(fm. |
|
|
46 | fs_path = os.path.join(fm.root_dir, *rel_path_list) | |
|
47 | 47 | self.assertEqual(path, fs_path) |
|
48 | 48 | |
|
49 |
fm = File |
|
|
49 | fm = FileContentsManager(root_dir=root) | |
|
50 | 50 | path = fm._get_os_path('test.ipynb') |
|
51 |
fs_path = os.path.join(fm. |
|
|
51 | fs_path = os.path.join(fm.root_dir, 'test.ipynb') | |
|
52 | 52 | self.assertEqual(path, fs_path) |
|
53 | 53 | |
|
54 |
fm = File |
|
|
54 | fm = FileContentsManager(root_dir=root) | |
|
55 | 55 | path = fm._get_os_path('test.ipynb', '////') |
|
56 |
fs_path = os.path.join(fm. |
|
|
56 | fs_path = os.path.join(fm.root_dir, 'test.ipynb') | |
|
57 | 57 | self.assertEqual(path, fs_path) |
|
58 | 58 | |
|
59 | 59 | def test_checkpoint_subdir(self): |
|
60 | 60 | subd = u'sub ∂ir' |
|
61 | 61 | cp_name = 'test-cp.ipynb' |
|
62 | 62 | with TemporaryDirectory() as td: |
|
63 |
|
|
|
63 | root = td | |
|
64 | 64 | os.mkdir(os.path.join(td, subd)) |
|
65 |
fm = File |
|
|
65 | fm = FileContentsManager(root_dir=root) | |
|
66 | 66 | cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/') |
|
67 | 67 | cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd) |
|
68 | 68 | self.assertNotEqual(cp_dir, cp_subdir) |
|
69 |
self.assertEqual(cp_dir, os.path.join( |
|
|
70 |
self.assertEqual(cp_subdir, os.path.join( |
|
|
69 | self.assertEqual(cp_dir, os.path.join(root, fm.checkpoint_dir, cp_name)) | |
|
70 | self.assertEqual(cp_subdir, os.path.join(root, subd, fm.checkpoint_dir, cp_name)) | |
|
71 | 71 | |
|
72 | 72 | |
|
73 |
class Test |
|
|
73 | class TestContentsManager(TestCase): | |
|
74 | 74 | |
|
75 | 75 | def setUp(self): |
|
76 | 76 | self._temp_dir = TemporaryDirectory() |
|
77 | 77 | self.td = self._temp_dir.name |
|
78 |
self. |
|
|
79 |
|
|
|
78 | self.contents_manager = FileContentsManager( | |
|
79 | root_dir=self.td, | |
|
80 | 80 | log=logging.getLogger() |
|
81 | 81 | ) |
|
82 | 82 | |
@@ -100,22 +100,22 b' class TestNotebookManager(TestCase):' | |||
|
100 | 100 | nb.worksheets[0].cells.append(cell) |
|
101 | 101 | |
|
102 | 102 | def new_notebook(self): |
|
103 |
|
|
|
104 |
model = |
|
|
103 | cm = self.contents_manager | |
|
104 | model = cm.create_file() | |
|
105 | 105 | name = model['name'] |
|
106 | 106 | path = model['path'] |
|
107 | 107 | |
|
108 |
full_model = |
|
|
108 | full_model = cm.get_model(name, path) | |
|
109 | 109 | nb = full_model['content'] |
|
110 | 110 | self.add_code_cell(nb) |
|
111 | 111 | |
|
112 |
|
|
|
112 | cm.save(full_model, name, path) | |
|
113 | 113 | return nb, name, path |
|
114 | 114 | |
|
115 |
def test_create_ |
|
|
116 |
|
|
|
115 | def test_create_file(self): | |
|
116 | cm = self.contents_manager | |
|
117 | 117 | # Test in root directory |
|
118 |
model = |
|
|
118 | model = cm.create_file() | |
|
119 | 119 | assert isinstance(model, dict) |
|
120 | 120 | self.assertIn('name', model) |
|
121 | 121 | self.assertIn('path', model) |
@@ -124,23 +124,23 b' class TestNotebookManager(TestCase):' | |||
|
124 | 124 | |
|
125 | 125 | # Test in sub-directory |
|
126 | 126 | sub_dir = '/foo/' |
|
127 |
self.make_dir( |
|
|
128 |
model = |
|
|
127 | self.make_dir(cm.root_dir, 'foo') | |
|
128 | model = cm.create_file(None, sub_dir) | |
|
129 | 129 | assert isinstance(model, dict) |
|
130 | 130 | self.assertIn('name', model) |
|
131 | 131 | self.assertIn('path', model) |
|
132 | 132 | self.assertEqual(model['name'], 'Untitled0.ipynb') |
|
133 | 133 | self.assertEqual(model['path'], sub_dir.strip('/')) |
|
134 | 134 | |
|
135 |
def test_get |
|
|
136 |
|
|
|
135 | def test_get(self): | |
|
136 | cm = self.contents_manager | |
|
137 | 137 | # Create a notebook |
|
138 |
model = |
|
|
138 | model = cm.create_file() | |
|
139 | 139 | name = model['name'] |
|
140 | 140 | path = model['path'] |
|
141 | 141 | |
|
142 | 142 | # Check that we 'get' on the notebook we just created |
|
143 |
model2 = |
|
|
143 | model2 = cm.get_model(name, path) | |
|
144 | 144 | assert isinstance(model2, dict) |
|
145 | 145 | self.assertIn('name', model2) |
|
146 | 146 | self.assertIn('path', model2) |
@@ -149,9 +149,9 b' class TestNotebookManager(TestCase):' | |||
|
149 | 149 | |
|
150 | 150 | # Test in sub-directory |
|
151 | 151 | sub_dir = '/foo/' |
|
152 |
self.make_dir( |
|
|
153 |
model = |
|
|
154 |
model2 = |
|
|
152 | self.make_dir(cm.root_dir, 'foo') | |
|
153 | model = cm.create_file(None, sub_dir) | |
|
154 | model2 = cm.get_model(name, sub_dir) | |
|
155 | 155 | assert isinstance(model2, dict) |
|
156 | 156 | self.assertIn('name', model2) |
|
157 | 157 | self.assertIn('path', model2) |
@@ -159,35 +159,35 b' class TestNotebookManager(TestCase):' | |||
|
159 | 159 | self.assertEqual(model2['name'], 'Untitled0.ipynb') |
|
160 | 160 | self.assertEqual(model2['path'], sub_dir.strip('/')) |
|
161 | 161 | |
|
162 |
def test_update |
|
|
163 |
|
|
|
162 | def test_update(self): | |
|
163 | cm = self.contents_manager | |
|
164 | 164 | # Create a notebook |
|
165 |
model = |
|
|
165 | model = cm.create_file() | |
|
166 | 166 | name = model['name'] |
|
167 | 167 | path = model['path'] |
|
168 | 168 | |
|
169 | 169 | # Change the name in the model for rename |
|
170 | 170 | model['name'] = 'test.ipynb' |
|
171 |
model = |
|
|
171 | model = cm.update(model, name, path) | |
|
172 | 172 | assert isinstance(model, dict) |
|
173 | 173 | self.assertIn('name', model) |
|
174 | 174 | self.assertIn('path', model) |
|
175 | 175 | self.assertEqual(model['name'], 'test.ipynb') |
|
176 | 176 | |
|
177 | 177 | # Make sure the old name is gone |
|
178 |
self.assertRaises(HTTPError, |
|
|
178 | self.assertRaises(HTTPError, cm.get_model, name, path) | |
|
179 | 179 | |
|
180 | 180 | # Test in sub-directory |
|
181 | 181 | # Create a directory and notebook in that directory |
|
182 | 182 | sub_dir = '/foo/' |
|
183 |
self.make_dir( |
|
|
184 |
model = |
|
|
183 | self.make_dir(cm.root_dir, 'foo') | |
|
184 | model = cm.create_file(None, sub_dir) | |
|
185 | 185 | name = model['name'] |
|
186 | 186 | path = model['path'] |
|
187 | 187 | |
|
188 | 188 | # Change the name in the model for rename |
|
189 | 189 | model['name'] = 'test_in_sub.ipynb' |
|
190 |
model = |
|
|
190 | model = cm.update(model, name, path) | |
|
191 | 191 | assert isinstance(model, dict) |
|
192 | 192 | self.assertIn('name', model) |
|
193 | 193 | self.assertIn('path', model) |
@@ -195,20 +195,20 b' class TestNotebookManager(TestCase):' | |||
|
195 | 195 | self.assertEqual(model['path'], sub_dir.strip('/')) |
|
196 | 196 | |
|
197 | 197 | # Make sure the old name is gone |
|
198 |
self.assertRaises(HTTPError, |
|
|
198 | self.assertRaises(HTTPError, cm.get_model, name, path) | |
|
199 | 199 | |
|
200 |
def test_save |
|
|
201 |
|
|
|
200 | def test_save(self): | |
|
201 | cm = self.contents_manager | |
|
202 | 202 | # Create a notebook |
|
203 |
model = |
|
|
203 | model = cm.create_file() | |
|
204 | 204 | name = model['name'] |
|
205 | 205 | path = model['path'] |
|
206 | 206 | |
|
207 | 207 | # Get the model with 'content' |
|
208 |
full_model = |
|
|
208 | full_model = cm.get_model(name, path) | |
|
209 | 209 | |
|
210 | 210 | # Save the notebook |
|
211 |
model = |
|
|
211 | model = cm.save(full_model, name, path) | |
|
212 | 212 | assert isinstance(model, dict) |
|
213 | 213 | self.assertIn('name', model) |
|
214 | 214 | self.assertIn('path', model) |
@@ -218,103 +218,84 b' class TestNotebookManager(TestCase):' | |||
|
218 | 218 | # Test in sub-directory |
|
219 | 219 | # Create a directory and notebook in that directory |
|
220 | 220 | sub_dir = '/foo/' |
|
221 |
self.make_dir( |
|
|
222 |
model = |
|
|
221 | self.make_dir(cm.root_dir, 'foo') | |
|
222 | model = cm.create_file(None, sub_dir) | |
|
223 | 223 | name = model['name'] |
|
224 | 224 | path = model['path'] |
|
225 |
model = |
|
|
225 | model = cm.get_model(name, path) | |
|
226 | 226 | |
|
227 | 227 | # Change the name in the model for rename |
|
228 |
model = |
|
|
228 | model = cm.save(model, name, path) | |
|
229 | 229 | assert isinstance(model, dict) |
|
230 | 230 | self.assertIn('name', model) |
|
231 | 231 | self.assertIn('path', model) |
|
232 | 232 | self.assertEqual(model['name'], 'Untitled0.ipynb') |
|
233 | 233 | self.assertEqual(model['path'], sub_dir.strip('/')) |
|
234 | 234 | |
|
235 |
def test_ |
|
|
236 |
|
|
|
237 | # Create a notebook | |
|
238 | model = nm.create_notebook() | |
|
239 | nm.save_script = True | |
|
240 | model = nm.create_notebook() | |
|
241 | name = model['name'] | |
|
242 | path = model['path'] | |
|
243 | ||
|
244 | # Get the model with 'content' | |
|
245 | full_model = nm.get_notebook(name, path) | |
|
246 | ||
|
247 | # Save the notebook | |
|
248 | model = nm.save_notebook(full_model, name, path) | |
|
249 | ||
|
250 | # Check that the script was created | |
|
251 | py_path = os.path.join(nm.notebook_dir, os.path.splitext(name)[0]+'.py') | |
|
252 | assert os.path.exists(py_path), py_path | |
|
253 | ||
|
254 | def test_delete_notebook(self): | |
|
255 | nm = self.notebook_manager | |
|
235 | def test_delete(self): | |
|
236 | cm = self.contents_manager | |
|
256 | 237 | # Create a notebook |
|
257 | 238 | nb, name, path = self.new_notebook() |
|
258 | 239 | |
|
259 | 240 | # Delete the notebook |
|
260 |
|
|
|
241 | cm.delete(name, path) | |
|
261 | 242 | |
|
262 | 243 | # Check that a 'get' on the deleted notebook raises and error |
|
263 |
self.assertRaises(HTTPError, |
|
|
244 | self.assertRaises(HTTPError, cm.get_model, name, path) | |
|
264 | 245 | |
|
265 |
def test_copy |
|
|
266 |
|
|
|
246 | def test_copy(self): | |
|
247 | cm = self.contents_manager | |
|
267 | 248 | path = u'å b' |
|
268 | 249 | name = u'nb √.ipynb' |
|
269 |
os.mkdir(os.path.join( |
|
|
270 |
orig = |
|
|
250 | os.mkdir(os.path.join(cm.root_dir, path)) | |
|
251 | orig = cm.create_file({'name' : name}, path=path) | |
|
271 | 252 | |
|
272 | 253 | # copy with unspecified name |
|
273 |
copy = |
|
|
254 | copy = cm.copy(name, path=path) | |
|
274 | 255 | self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb')) |
|
275 | 256 | |
|
276 | 257 | # copy with specified name |
|
277 |
copy2 = |
|
|
258 | copy2 = cm.copy(name, u'copy 2.ipynb', path=path) | |
|
278 | 259 | self.assertEqual(copy2['name'], u'copy 2.ipynb') |
|
279 | 260 | |
|
280 | 261 | def test_trust_notebook(self): |
|
281 |
|
|
|
262 | cm = self.contents_manager | |
|
282 | 263 | nb, name, path = self.new_notebook() |
|
283 | 264 | |
|
284 |
untrusted = |
|
|
285 |
assert not |
|
|
265 | untrusted = cm.get_model(name, path)['content'] | |
|
266 | assert not cm.notary.check_cells(untrusted) | |
|
286 | 267 | |
|
287 | 268 | # print(untrusted) |
|
288 |
|
|
|
289 |
trusted = |
|
|
269 | cm.trust_notebook(name, path) | |
|
270 | trusted = cm.get_model(name, path)['content'] | |
|
290 | 271 | # print(trusted) |
|
291 |
assert |
|
|
272 | assert cm.notary.check_cells(trusted) | |
|
292 | 273 | |
|
293 | 274 | def test_mark_trusted_cells(self): |
|
294 |
|
|
|
275 | cm = self.contents_manager | |
|
295 | 276 | nb, name, path = self.new_notebook() |
|
296 | 277 | |
|
297 |
|
|
|
278 | cm.mark_trusted_cells(nb, name, path) | |
|
298 | 279 | for cell in nb.worksheets[0].cells: |
|
299 | 280 | if cell.cell_type == 'code': |
|
300 | 281 | assert not cell.trusted |
|
301 | 282 | |
|
302 |
|
|
|
303 |
nb = |
|
|
283 | cm.trust_notebook(name, path) | |
|
284 | nb = cm.get_model(name, path)['content'] | |
|
304 | 285 | for cell in nb.worksheets[0].cells: |
|
305 | 286 | if cell.cell_type == 'code': |
|
306 | 287 | assert cell.trusted |
|
307 | 288 | |
|
308 | 289 | def test_check_and_sign(self): |
|
309 |
|
|
|
290 | cm = self.contents_manager | |
|
310 | 291 | nb, name, path = self.new_notebook() |
|
311 | 292 | |
|
312 |
|
|
|
313 |
|
|
|
314 |
assert not |
|
|
293 | cm.mark_trusted_cells(nb, name, path) | |
|
294 | cm.check_and_sign(nb, name, path) | |
|
295 | assert not cm.notary.check_signature(nb) | |
|
315 | 296 | |
|
316 |
|
|
|
317 |
nb = |
|
|
318 |
|
|
|
319 |
|
|
|
320 |
assert |
|
|
297 | cm.trust_notebook(name, path) | |
|
298 | nb = cm.get_model(name, path)['content'] | |
|
299 | cm.mark_trusted_cells(nb, name, path) | |
|
300 | cm.check_and_sign(nb, name, path) | |
|
301 | assert cm.notary.check_signature(nb) |
@@ -27,8 +27,16 b' class MainKernelHandler(IPythonHandler):' | |||
|
27 | 27 | @web.authenticated |
|
28 | 28 | @json_errors |
|
29 | 29 | def post(self): |
|
30 | model = self.get_json_body() | |
|
31 | if model is None: | |
|
32 | raise web.HTTPError(400, "No JSON data provided") | |
|
33 | try: | |
|
34 | name = model['name'] | |
|
35 | except KeyError: | |
|
36 | raise web.HTTPError(400, "Missing field in JSON data: name") | |
|
37 | ||
|
30 | 38 | km = self.kernel_manager |
|
31 | kernel_id = km.start_kernel() | |
|
39 | kernel_id = km.start_kernel(kernel_name=name) | |
|
32 | 40 | model = km.kernel_model(kernel_id) |
|
33 | 41 | location = url_path_join(self.base_url, 'api', 'kernels', kernel_id) |
|
34 | 42 | self.set_header('Location', url_escape(location)) |
@@ -76,6 +84,9 b' class KernelActionHandler(IPythonHandler):' | |||
|
76 | 84 | |
|
77 | 85 | class ZMQChannelHandler(AuthenticatedZMQStreamHandler): |
|
78 | 86 | |
|
87 | def __repr__(self): | |
|
88 | return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized')) | |
|
89 | ||
|
79 | 90 | def create_stream(self): |
|
80 | 91 | km = self.kernel_manager |
|
81 | 92 | meth = getattr(km, 'connect_%s' % self.channel) |
@@ -137,6 +148,12 b' class ZMQChannelHandler(AuthenticatedZMQStreamHandler):' | |||
|
137 | 148 | self.zmq_stream.on_recv(self._on_zmq_reply) |
|
138 | 149 | |
|
139 | 150 | def on_message(self, msg): |
|
151 | if self.zmq_stream is None: | |
|
152 | return | |
|
153 | elif self.zmq_stream.closed(): | |
|
154 | self.log.info("%s closed, closing websocket.", self) | |
|
155 | self.close() | |
|
156 | return | |
|
140 | 157 | msg = json.loads(msg) |
|
141 | 158 | self.session.send(self.zmq_stream, msg) |
|
142 | 159 |
@@ -72,8 +72,8 b' class MappingKernelManager(MultiKernelManager):' | |||
|
72 | 72 | os_path = os.path.dirname(os_path) |
|
73 | 73 | return os_path |
|
74 | 74 | |
|
75 | def start_kernel(self, kernel_id=None, path=None, **kwargs): | |
|
76 | """Start a kernel for a session an return its kernel_id. | |
|
75 | def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs): | |
|
76 | """Start a kernel for a session and return its kernel_id. | |
|
77 | 77 | |
|
78 | 78 | Parameters |
|
79 | 79 | ---------- |
@@ -84,12 +84,16 b' class MappingKernelManager(MultiKernelManager):' | |||
|
84 | 84 | path : API path |
|
85 | 85 | The API path (unicode, '/' delimited) for the cwd. |
|
86 | 86 | Will be transformed to an OS path relative to root_dir. |
|
87 | kernel_name : str | |
|
88 | The name identifying which kernel spec to launch. This is ignored if | |
|
89 | an existing kernel is returned, but it may be checked in the future. | |
|
87 | 90 | """ |
|
88 | 91 | if kernel_id is None: |
|
89 | 92 | kwargs['extra_arguments'] = self.kernel_argv |
|
90 | 93 | if path is not None: |
|
91 | 94 | kwargs['cwd'] = self.cwd_for_path(path) |
|
92 |
kernel_id = super(MappingKernelManager, self).start_kernel( |
|
|
95 | kernel_id = super(MappingKernelManager, self).start_kernel( | |
|
96 | kernel_name=kernel_name, **kwargs) | |
|
93 | 97 | self.log.info("Kernel started: %s" % kernel_id) |
|
94 | 98 | self.log.debug("Kernel args: %r" % kwargs) |
|
95 | 99 | # register callback for failed auto-restart |
@@ -111,7 +115,8 b' class MappingKernelManager(MultiKernelManager):' | |||
|
111 | 115 | """Return a dictionary of kernel information described in the |
|
112 | 116 | JSON standard model.""" |
|
113 | 117 | self._check_kernel_id(kernel_id) |
|
114 |
model = {"id":kernel_id |
|
|
118 | model = {"id":kernel_id, | |
|
119 | "name": self._kernels[kernel_id].kernel_name} | |
|
115 | 120 | return model |
|
116 | 121 | |
|
117 | 122 | def list_kernels(self): |
@@ -1,6 +1,6 b'' | |||
|
1 | 1 | """Test the kernels service API.""" |
|
2 | 2 | |
|
3 | ||
|
3 | import json | |
|
4 | 4 | import requests |
|
5 | 5 | |
|
6 | 6 | from IPython.html.utils import url_path_join |
@@ -30,8 +30,9 b' class KernelAPI(object):' | |||
|
30 | 30 | def get(self, id): |
|
31 | 31 | return self._req('GET', id) |
|
32 | 32 | |
|
33 | def start(self): | |
|
34 | return self._req('POST', '') | |
|
33 | def start(self, name='python'): | |
|
34 | body = json.dumps({'name': name}) | |
|
35 | return self._req('POST', '', body) | |
|
35 | 36 | |
|
36 | 37 | def shutdown(self, id): |
|
37 | 38 | return self._req('DELETE', id) |
@@ -64,11 +65,14 b' class KernelAPITest(NotebookTestBase):' | |||
|
64 | 65 | self.assertEqual(r.status_code, 201) |
|
65 | 66 | self.assertIsInstance(kern1, dict) |
|
66 | 67 | |
|
68 | self.assertEqual(r.headers['x-frame-options'], "SAMEORIGIN") | |
|
69 | ||
|
67 | 70 | # GET request |
|
68 | 71 | r = self.kern_api.list() |
|
69 | 72 | self.assertEqual(r.status_code, 200) |
|
70 | 73 | assert isinstance(r.json(), list) |
|
71 | 74 | self.assertEqual(r.json()[0]['id'], kern1['id']) |
|
75 | self.assertEqual(r.json()[0]['name'], kern1['name']) | |
|
72 | 76 | |
|
73 | 77 | # create another kernel and check that they both are added to the |
|
74 | 78 | # list of kernels from a GET request |
@@ -89,6 +93,7 b' class KernelAPITest(NotebookTestBase):' | |||
|
89 | 93 | self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id']) |
|
90 | 94 | rekern = r.json() |
|
91 | 95 | self.assertEqual(rekern['id'], kern2['id']) |
|
96 | self.assertEqual(rekern['name'], kern2['name']) | |
|
92 | 97 | |
|
93 | 98 | def test_kernel_handler(self): |
|
94 | 99 | # GET kernel with given id |
@@ -7,6 +7,8 b' from tornado import web' | |||
|
7 | 7 | |
|
8 | 8 | from ...base.handlers import IPythonHandler, json_errors |
|
9 | 9 | |
|
10 | from IPython.kernel.kernelspec import _pythonfirst | |
|
11 | ||
|
10 | 12 | |
|
11 | 13 | class MainKernelSpecHandler(IPythonHandler): |
|
12 | 14 | SUPPORTED_METHODS = ('GET',) |
@@ -16,7 +18,7 b' class MainKernelSpecHandler(IPythonHandler):' | |||
|
16 | 18 | def get(self): |
|
17 | 19 | ksm = self.kernel_spec_manager |
|
18 | 20 | results = [] |
|
19 | for kernel_name in ksm.find_kernel_specs(): | |
|
21 | for kernel_name in sorted(ksm.find_kernel_specs(), key=_pythonfirst): | |
|
20 | 22 | d = ksm.get_kernel_spec(kernel_name).to_dict() |
|
21 | 23 | d['name'] = kernel_name |
|
22 | 24 | results.append(d) |
@@ -1,20 +1,7 b'' | |||
|
1 | """Tornado handlers for the sessions web service. | |
|
1 | """Tornado handlers for the sessions web service.""" | |
|
2 | 2 | |
|
3 | Authors: | |
|
4 | ||
|
5 | * Zach Sailer | |
|
6 | """ | |
|
7 | ||
|
8 | #----------------------------------------------------------------------------- | |
|
9 | # Copyright (C) 2013 The IPython Development Team | |
|
10 | # | |
|
11 | # Distributed under the terms of the BSD License. The full license is in | |
|
12 | # the file COPYING, distributed as part of this software. | |
|
13 | #----------------------------------------------------------------------------- | |
|
14 | ||
|
15 | #----------------------------------------------------------------------------- | |
|
16 | # Imports | |
|
17 | #----------------------------------------------------------------------------- | |
|
3 | # Copyright (c) IPython Development Team. | |
|
4 | # Distributed under the terms of the Modified BSD License. | |
|
18 | 5 | |
|
19 | 6 | import json |
|
20 | 7 | |
@@ -24,10 +11,6 b' from ...base.handlers import IPythonHandler, json_errors' | |||
|
24 | 11 | from IPython.utils.jsonutil import date_default |
|
25 | 12 | from IPython.html.utils import url_path_join, url_escape |
|
26 | 13 | |
|
27 | #----------------------------------------------------------------------------- | |
|
28 | # Session web service handlers | |
|
29 | #----------------------------------------------------------------------------- | |
|
30 | ||
|
31 | 14 | |
|
32 | 15 | class SessionRootHandler(IPythonHandler): |
|
33 | 16 | |
@@ -45,27 +28,30 b' class SessionRootHandler(IPythonHandler):' | |||
|
45 | 28 | # Creates a new session |
|
46 | 29 | #(unless a session already exists for the named nb) |
|
47 | 30 | sm = self.session_manager |
|
48 |
|
|
|
31 | cm = self.contents_manager | |
|
49 | 32 | km = self.kernel_manager |
|
33 | ||
|
50 | 34 | model = self.get_json_body() |
|
51 | 35 | if model is None: |
|
52 | 36 | raise web.HTTPError(400, "No JSON data provided") |
|
53 | 37 | try: |
|
54 | 38 | name = model['notebook']['name'] |
|
55 | 39 | except KeyError: |
|
56 | raise web.HTTPError(400, "Missing field in JSON data: name") | |
|
40 | raise web.HTTPError(400, "Missing field in JSON data: notebook.name") | |
|
57 | 41 | try: |
|
58 | 42 | path = model['notebook']['path'] |
|
59 | 43 | except KeyError: |
|
60 | raise web.HTTPError(400, "Missing field in JSON data: path") | |
|
44 | raise web.HTTPError(400, "Missing field in JSON data: notebook.path") | |
|
45 | try: | |
|
46 | kernel_name = model['kernel']['name'] | |
|
47 | except KeyError: | |
|
48 | raise web.HTTPError(400, "Missing field in JSON data: kernel.name") | |
|
49 | ||
|
61 | 50 | # Check to see if session exists |
|
62 | 51 | if sm.session_exists(name=name, path=path): |
|
63 | 52 | model = sm.get_session(name=name, path=path) |
|
64 | 53 | else: |
|
65 | # allow nbm to specify kernels cwd | |
|
66 | kernel_path = nbm.get_kernel_path(name=name, path=path) | |
|
67 | kernel_id = km.start_kernel(path=kernel_path) | |
|
68 | model = sm.create_session(name=name, path=path, kernel_id=kernel_id) | |
|
54 | model = sm.create_session(name=name, path=path, kernel_name=kernel_name) | |
|
69 | 55 | location = url_path_join(self.base_url, 'api', 'sessions', model['id']) |
|
70 | 56 | self.set_header('Location', url_escape(location)) |
|
71 | 57 | self.set_status(201) |
@@ -108,10 +94,7 b' class SessionHandler(IPythonHandler):' | |||
|
108 | 94 | def delete(self, session_id): |
|
109 | 95 | # Deletes the session with given session_id |
|
110 | 96 | sm = self.session_manager |
|
111 | km = self.kernel_manager | |
|
112 | session = sm.get_session(session_id=session_id) | |
|
113 | 97 | sm.delete_session(session_id) |
|
114 | km.shutdown_kernel(session['kernel']['id']) | |
|
115 | 98 | self.set_status(204) |
|
116 | 99 | self.finish() |
|
117 | 100 |
@@ -23,6 +23,7 b' from tornado import web' | |||
|
23 | 23 | |
|
24 | 24 | from IPython.config.configurable import LoggingConfigurable |
|
25 | 25 | from IPython.utils.py3compat import unicode_type |
|
26 | from IPython.utils.traitlets import Instance | |
|
26 | 27 | |
|
27 | 28 | #----------------------------------------------------------------------------- |
|
28 | 29 | # Classes |
@@ -30,6 +31,9 b' from IPython.utils.py3compat import unicode_type' | |||
|
30 | 31 | |
|
31 | 32 | class SessionManager(LoggingConfigurable): |
|
32 | 33 | |
|
34 | kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager') | |
|
35 | contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=()) | |
|
36 | ||
|
33 | 37 | # Session database initialized below |
|
34 | 38 | _cursor = None |
|
35 | 39 | _connection = None |
@@ -69,10 +73,15 b' class SessionManager(LoggingConfigurable):' | |||
|
69 | 73 | "Create a uuid for a new session" |
|
70 | 74 | return unicode_type(uuid.uuid4()) |
|
71 | 75 | |
|
72 |
def create_session(self, name=None, path=None, kernel_ |
|
|
76 | def create_session(self, name=None, path=None, kernel_name='python'): | |
|
73 | 77 | """Creates a session and returns its model""" |
|
74 | 78 | session_id = self.new_session_id() |
|
75 | return self.save_session(session_id, name=name, path=path, kernel_id=kernel_id) | |
|
79 | # allow nbm to specify kernels cwd | |
|
80 | kernel_path = self.contents_manager.get_kernel_path(name=name, path=path) | |
|
81 | kernel_id = self.kernel_manager.start_kernel(path=kernel_path, | |
|
82 | kernel_name=kernel_name) | |
|
83 | return self.save_session(session_id, name=name, path=path, | |
|
84 | kernel_id=kernel_id) | |
|
76 | 85 | |
|
77 | 86 | def save_session(self, session_id, name=None, path=None, kernel_id=None): |
|
78 | 87 | """Saves the items for the session with the given session_id |
@@ -170,8 +179,7 b' class SessionManager(LoggingConfigurable):' | |||
|
170 | 179 | query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets)) |
|
171 | 180 | self.cursor.execute(query, list(kwargs.values()) + [session_id]) |
|
172 | 181 | |
|
173 | @staticmethod | |
|
174 | def row_factory(cursor, row): | |
|
182 | def row_factory(self, cursor, row): | |
|
175 | 183 | """Takes sqlite database session row and turns it into a dictionary""" |
|
176 | 184 | row = sqlite3.Row(cursor, row) |
|
177 | 185 | model = { |
@@ -180,9 +188,7 b' class SessionManager(LoggingConfigurable):' | |||
|
180 | 188 | 'name': row['name'], |
|
181 | 189 | 'path': row['path'] |
|
182 | 190 | }, |
|
183 | 'kernel': { | |
|
184 | 'id': row['kernel_id'], | |
|
185 | } | |
|
191 | 'kernel': self.kernel_manager.kernel_model(row['kernel_id']) | |
|
186 | 192 | } |
|
187 | 193 | return model |
|
188 | 194 | |
@@ -195,5 +201,6 b' class SessionManager(LoggingConfigurable):' | |||
|
195 | 201 | def delete_session(self, session_id): |
|
196 | 202 | """Deletes the row in the session database with given session_id""" |
|
197 | 203 | # Check that session exists before deleting |
|
198 | self.get_session(session_id=session_id) | |
|
204 | session = self.get_session(session_id=session_id) | |
|
205 | self.kernel_manager.shutdown_kernel(session['kernel']['id']) | |
|
199 | 206 | self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,)) |
@@ -5,79 +5,101 b' from unittest import TestCase' | |||
|
5 | 5 | from tornado import web |
|
6 | 6 | |
|
7 | 7 | from ..sessionmanager import SessionManager |
|
8 | from IPython.html.services.kernels.kernelmanager import MappingKernelManager | |
|
9 | ||
|
10 | class DummyKernel(object): | |
|
11 | def __init__(self, kernel_name='python'): | |
|
12 | self.kernel_name = kernel_name | |
|
13 | ||
|
14 | class DummyMKM(MappingKernelManager): | |
|
15 | """MappingKernelManager interface that doesn't start kernels, for testing""" | |
|
16 | def __init__(self, *args, **kwargs): | |
|
17 | super(DummyMKM, self).__init__(*args, **kwargs) | |
|
18 | self.id_letters = iter(u'ABCDEFGHIJK') | |
|
19 | ||
|
20 | def _new_id(self): | |
|
21 | return next(self.id_letters) | |
|
22 | ||
|
23 | def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs): | |
|
24 | kernel_id = kernel_id or self._new_id() | |
|
25 | self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name) | |
|
26 | return kernel_id | |
|
27 | ||
|
28 | def shutdown_kernel(self, kernel_id, now=False): | |
|
29 | del self._kernels[kernel_id] | |
|
8 | 30 | |
|
9 | 31 | class TestSessionManager(TestCase): |
|
10 | 32 | |
|
11 | 33 | def test_get_session(self): |
|
12 | sm = SessionManager() | |
|
13 | session_id = sm.new_session_id() | |
|
14 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') | |
|
34 | sm = SessionManager(kernel_manager=DummyMKM()) | |
|
35 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
|
36 | kernel_name='bar')['id'] | |
|
15 | 37 | model = sm.get_session(session_id=session_id) |
|
16 | expected = {'id':session_id, 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}} | |
|
38 | expected = {'id':session_id, | |
|
39 | 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, | |
|
40 | 'kernel': {'id':u'A', 'name': 'bar'}} | |
|
17 | 41 | self.assertEqual(model, expected) |
|
18 | 42 | |
|
19 | 43 | def test_bad_get_session(self): |
|
20 | 44 | # Should raise error if a bad key is passed to the database. |
|
21 | sm = SessionManager() | |
|
22 | session_id = sm.new_session_id() | |
|
23 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') | |
|
45 | sm = SessionManager(kernel_manager=DummyMKM()) | |
|
46 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
|
47 | kernel_name='foo')['id'] | |
|
24 | 48 | self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword |
|
25 | 49 | |
|
26 | 50 | def test_list_sessions(self): |
|
27 | sm = SessionManager() | |
|
28 | session_id1 = sm.new_session_id() | |
|
29 | session_id2 = sm.new_session_id() | |
|
30 | session_id3 = sm.new_session_id() | |
|
31 |
sm. |
|
|
32 | sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678') | |
|
33 | sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678') | |
|
51 | sm = SessionManager(kernel_manager=DummyMKM()) | |
|
52 | sessions = [ | |
|
53 | sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'), | |
|
54 | sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'), | |
|
55 | sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'), | |
|
56 | ] | |
|
34 | 57 | sessions = sm.list_sessions() |
|
35 |
expected = [{'id':session |
|
|
36 |
'path': u'/path/to/1/'}, 'kernel':{'id':u' |
|
|
37 |
{'id':session |
|
|
38 |
'path': u'/path/to/2/'}, 'kernel':{'id':u' |
|
|
39 |
{'id':session |
|
|
40 |
'path': u'/path/to/3/'}, 'kernel':{'id':u' |
|
|
58 | expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb', | |
|
59 | 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}}, | |
|
60 | {'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb', | |
|
61 | 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}}, | |
|
62 | {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb', | |
|
63 | 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}] | |
|
41 | 64 | self.assertEqual(sessions, expected) |
|
42 | 65 | |
|
43 | 66 | def test_update_session(self): |
|
44 | sm = SessionManager() | |
|
45 | session_id = sm.new_session_id() | |
|
46 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id=None) | |
|
47 | sm.update_session(session_id, kernel_id='5678') | |
|
67 | sm = SessionManager(kernel_manager=DummyMKM()) | |
|
68 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
|
69 | kernel_name='julia')['id'] | |
|
48 | 70 | sm.update_session(session_id, name='new_name.ipynb') |
|
49 | 71 | model = sm.get_session(session_id=session_id) |
|
50 | expected = {'id':session_id, 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}} | |
|
72 | expected = {'id':session_id, | |
|
73 | 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, | |
|
74 | 'kernel':{'id':u'A', 'name':'julia'}} | |
|
51 | 75 | self.assertEqual(model, expected) |
|
52 | 76 | |
|
53 | 77 | def test_bad_update_session(self): |
|
54 | 78 | # try to update a session with a bad keyword ~ raise error |
|
55 | sm = SessionManager() | |
|
56 | session_id = sm.new_session_id() | |
|
57 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') | |
|
79 | sm = SessionManager(kernel_manager=DummyMKM()) | |
|
80 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
|
81 | kernel_name='ir')['id'] | |
|
58 | 82 | self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword |
|
59 | 83 | |
|
60 | 84 | def test_delete_session(self): |
|
61 | sm = SessionManager() | |
|
62 | session_id1 = sm.new_session_id() | |
|
63 | session_id2 = sm.new_session_id() | |
|
64 | session_id3 = sm.new_session_id() | |
|
65 |
sm. |
|
|
66 | sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678') | |
|
67 | sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678') | |
|
68 |
sm. |
|
|
69 | sessions = sm.list_sessions() | |
|
70 | expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb', | |
|
71 | 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}}, | |
|
72 | {'id':session_id3, 'notebook':{'name':u'test3.ipynb', | |
|
73 | 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}] | |
|
74 | self.assertEqual(sessions, expected) | |
|
85 | sm = SessionManager(kernel_manager=DummyMKM()) | |
|
86 | sessions = [ | |
|
87 | sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'), | |
|
88 | sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'), | |
|
89 | sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'), | |
|
90 | ] | |
|
91 | sm.delete_session(sessions[1]['id']) | |
|
92 | new_sessions = sm.list_sessions() | |
|
93 | expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb', | |
|
94 | 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}}, | |
|
95 | {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb', | |
|
96 | 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}] | |
|
97 | self.assertEqual(new_sessions, expected) | |
|
75 | 98 | |
|
76 | 99 | def test_bad_delete_session(self): |
|
77 | 100 | # try to delete a session that doesn't exist ~ raise error |
|
78 | sm = SessionManager() | |
|
79 | session_id = sm.new_session_id() | |
|
80 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') | |
|
101 | sm = SessionManager(kernel_manager=DummyMKM()) | |
|
102 | sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python') | |
|
81 | 103 | self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword |
|
82 | 104 | self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant |
|
83 | 105 |
@@ -37,8 +37,9 b' class SessionAPI(object):' | |||
|
37 | 37 | def get(self, id): |
|
38 | 38 | return self._req('GET', id) |
|
39 | 39 | |
|
40 | def create(self, name, path): | |
|
41 |
body = json.dumps({'notebook': {'name':name, 'path':path} |
|
|
40 | def create(self, name, path, kernel_name='python'): | |
|
41 | body = json.dumps({'notebook': {'name':name, 'path':path}, | |
|
42 | 'kernel': {'name': kernel_name}}) | |
|
42 | 43 | return self._req('POST', '', body) |
|
43 | 44 | |
|
44 | 45 | def modify(self, id, name, path): |
@@ -1,21 +1,12 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
7 | 3 | |
|
8 | //============================================================================ | |
|
9 | // On document ready | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | ||
|
13 | $(document).ready(function () { | |
|
14 | ||
|
15 | IPython.page = new IPython.Page(); | |
|
4 | var ipython = ipython || {}; | |
|
5 | require(['base/js/page'], function(page) { | |
|
6 | var page_instance = new page.Page(); | |
|
16 | 7 | $('button#login_submit').addClass("btn btn-default"); |
|
17 |
|
|
|
8 | page_instance.show(); | |
|
18 | 9 | $('input#password_input').focus(); |
|
19 | 10 | |
|
11 | ipython.page = page_instance; | |
|
20 | 12 | }); |
|
21 |
@@ -1,43 +1,35 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // Login button | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | var IPython = (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'base/js/utils', | |
|
7 | 'jquery', | |
|
8 | ], function(IPython, utils, $){ | |
|
13 | 9 | "use strict"; |
|
14 | 10 | |
|
15 | 11 | var LoginWidget = function (selector, options) { |
|
16 | 12 | options = options || {}; |
|
17 |
this.base_url = options.base_url || |
|
|
13 | this.base_url = options.base_url || utils.get_body_data("baseUrl"); | |
|
18 | 14 | this.selector = selector; |
|
19 | 15 | if (this.selector !== undefined) { |
|
20 | 16 | this.element = $(selector); |
|
21 | this.style(); | |
|
22 | 17 | this.bind_events(); |
|
23 | 18 | } |
|
24 | 19 | }; |
|
25 | 20 | |
|
26 | LoginWidget.prototype.style = function () { | |
|
27 | this.element.find("button").addClass("btn btn-default btn-sm"); | |
|
28 | }; | |
|
29 | 21 | |
|
30 | 22 | |
|
31 | 23 | LoginWidget.prototype.bind_events = function () { |
|
32 | 24 | var that = this; |
|
33 | 25 | this.element.find("button#logout").click(function () { |
|
34 |
window.location = |
|
|
26 | window.location = utils.url_join_encode( | |
|
35 | 27 | that.base_url, |
|
36 | 28 | "logout" |
|
37 | 29 | ); |
|
38 | 30 | }); |
|
39 | 31 | this.element.find("button#login").click(function () { |
|
40 |
window.location = |
|
|
32 | window.location = utils.url_join_encode( | |
|
41 | 33 | that.base_url, |
|
42 | 34 | "login" |
|
43 | 35 | ); |
@@ -47,6 +39,5 b' var IPython = (function (IPython) {' | |||
|
47 | 39 | // Set module variables |
|
48 | 40 | IPython.LoginWidget = LoginWidget; |
|
49 | 41 | |
|
50 | return IPython; | |
|
51 | ||
|
52 | }(IPython)); | |
|
42 | return {'LoginWidget': LoginWidget}; | |
|
43 | }); |
@@ -1,20 +1,10 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
7 | 3 | |
|
8 | //============================================================================ | |
|
9 | // On document ready | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | ||
|
13 | $(document).ready(function () { | |
|
14 | ||
|
15 | IPython.page = new IPython.Page(); | |
|
16 | $('#ipython-main-app').addClass('border-box-sizing'); | |
|
17 | IPython.page.show(); | |
|
4 | var ipython = ipython || {}; | |
|
5 | require(['base/js/page'], function(page) { | |
|
6 | var page_instance = new page.Page(); | |
|
7 | page_instance.show(); | |
|
18 | 8 | |
|
9 | ipython.page = page_instance; | |
|
19 | 10 | }); |
|
20 |
@@ -1,2 +1,7 b'' | |||
|
1 | /*! | |
|
2 | * | |
|
3 | * IPython auth | |
|
4 | * | |
|
5 | */ | |
|
1 | 6 | @import "login.less"; |
|
2 | 7 | @import "logout.less"; No newline at end of file |
@@ -1,20 +1,14 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2013 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
7 | 3 | |
|
8 | //============================================================================ | |
|
9 | // Utility for modal dialogs with bootstrap | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | IPython.namespace('IPython.dialog'); | |
|
13 | ||
|
14 | IPython.dialog = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | ], function(IPython, $) { | |
|
15 | 8 | "use strict"; |
|
16 | 9 | |
|
17 | 10 | var modal = function (options) { |
|
11 | ||
|
18 | 12 | var modal = $("<div/>") |
|
19 | 13 | .addClass("modal") |
|
20 | 14 | .addClass("fade") |
@@ -79,26 +73,28 b' IPython.dialog = (function (IPython) {' | |||
|
79 | 73 | }); |
|
80 | 74 | } |
|
81 | 75 | modal.on("hidden.bs.modal", function () { |
|
82 |
if ( |
|
|
83 |
var cell = |
|
|
76 | if (options.notebook) { | |
|
77 | var cell = options.notebook.get_selected_cell(); | |
|
84 | 78 | if (cell) cell.select(); |
|
85 | IPython.keyboard_manager.enable(); | |
|
86 |
|
|
|
79 | } | |
|
80 | if (options.keyboard_manager) { | |
|
81 | options.keyboard_manager.enable(); | |
|
82 | options.keyboard_manager.command_mode(); | |
|
87 | 83 | } |
|
88 | 84 | }); |
|
89 | 85 | |
|
90 |
if ( |
|
|
91 |
|
|
|
86 | if (options.keyboard_manager) { | |
|
87 | options.keyboard_manager.disable(); | |
|
92 | 88 | } |
|
93 | 89 | |
|
94 | 90 | return modal.modal(options); |
|
95 | 91 | }; |
|
96 | 92 | |
|
97 |
var edit_metadata = function ( |
|
|
98 | name = name || "Cell"; | |
|
93 | var edit_metadata = function (options) { | |
|
94 | options.name = options.name || "Cell"; | |
|
99 | 95 | var error_div = $('<div/>').css('color', 'red'); |
|
100 | 96 | var message = |
|
101 | "Manually edit the JSON below to manipulate the metadata for this " + name + "." + | |
|
97 | "Manually edit the JSON below to manipulate the metadata for this " + options.name + "." + | |
|
102 | 98 | " We recommend putting custom metadata attributes in an appropriately named sub-structure," + |
|
103 | 99 | " so they don't conflict with those of others."; |
|
104 | 100 | |
@@ -106,7 +102,7 b' IPython.dialog = (function (IPython) {' | |||
|
106 | 102 | .attr('rows', '13') |
|
107 | 103 | .attr('cols', '80') |
|
108 | 104 | .attr('name', 'metadata') |
|
109 | .text(JSON.stringify(md || {}, null, 2)); | |
|
105 | .text(JSON.stringify(options.md || {}, null, 2)); | |
|
110 | 106 | |
|
111 | 107 | var dialogform = $('<div/>').attr('title', 'Edit the metadata') |
|
112 | 108 | .append( |
@@ -128,8 +124,8 b' IPython.dialog = (function (IPython) {' | |||
|
128 | 124 | autoIndent: true, |
|
129 | 125 | mode: 'application/json', |
|
130 | 126 | }); |
|
131 |
var modal = |
|
|
132 | title: "Edit " + name + " Metadata", | |
|
127 | var modal_obj = modal({ | |
|
128 | title: "Edit " + options.name + " Metadata", | |
|
133 | 129 | body: dialogform, |
|
134 | 130 | buttons: { |
|
135 | 131 | OK: { class : "btn-primary", |
@@ -143,19 +139,25 b' IPython.dialog = (function (IPython) {' | |||
|
143 | 139 | error_div.text('WARNING: Could not save invalid JSON.'); |
|
144 | 140 | return false; |
|
145 | 141 | } |
|
146 | callback(new_md); | |
|
142 | options.callback(new_md); | |
|
147 | 143 | } |
|
148 | 144 | }, |
|
149 | 145 | Cancel: {} |
|
150 | } | |
|
146 | }, | |
|
147 | notebook: options.notebook, | |
|
148 | keyboard_manager: options.keyboard_manager, | |
|
151 | 149 | }); |
|
152 | 150 | |
|
153 | modal.on('shown.bs.modal', function(){ editor.refresh(); }); | |
|
151 | modal_obj.on('shown.bs.modal', function(){ editor.refresh(); }); | |
|
154 | 152 | }; |
|
155 | 153 | |
|
156 | return { | |
|
154 | var dialog = { | |
|
157 | 155 | modal : modal, |
|
158 | 156 | edit_metadata : edit_metadata, |
|
159 | 157 | }; |
|
160 | 158 | |
|
161 | }(IPython)); | |
|
159 | // Backwards compatability. | |
|
160 | IPython.dialog = dialog; | |
|
161 | ||
|
162 | return dialog; | |
|
163 | }); |
@@ -1,32 +1,24 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // Events | |
|
10 | //============================================================================ | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
11 | 3 | |
|
12 | 4 | // Give us an object to bind all events to. This object should be created |
|
13 | 5 | // before all other objects so it exists when others register event handlers. |
|
14 |
// To |
|
|
15 | // $([IPython.events]).trigger('event.Namespace'); | |
|
16 | // To handle it: | |
|
17 |
// |
|
|
6 | // To register an event handler: | |
|
7 | // | |
|
8 | // require(['base/js/events'], function (events) { | |
|
9 | // events.on("event.Namespace", function () { do_stuff(); }); | |
|
10 | // }); | |
|
18 | 11 | |
|
19 | var IPython = (function (IPython) { | |
|
12 | define(['base/js/namespace', 'jquery'], function(IPython, $) { | |
|
20 | 13 | "use strict"; |
|
21 | 14 | |
|
22 | var utils = IPython.utils; | |
|
23 | ||
|
24 | 15 | var Events = function () {}; |
|
25 | 16 | |
|
26 |
|
|
|
27 | IPython.events = new Events(); | |
|
28 | ||
|
29 | return IPython; | |
|
17 | var events = new Events(); | |
|
30 | 18 | |
|
31 | }(IPython)); | |
|
19 | // Backwards compatability. | |
|
20 | IPython.Events = Events; | |
|
21 | IPython.events = events; | |
|
32 | 22 | |
|
23 | return $([events]); | |
|
24 | }); |
@@ -1,19 +1,14 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
7 | 3 | |
|
8 | //============================================================================ | |
|
9 | // Keyboard management | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | IPython.namespace('IPython.keyboard'); | |
|
13 | ||
|
14 | IPython.keyboard = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | ], function(IPython, $, utils) { | |
|
15 | 9 | "use strict"; |
|
16 | 10 | |
|
11 | ||
|
17 | 12 | // Setup global keycodes and inverse keycodes. |
|
18 | 13 | |
|
19 | 14 | // See http://unixpapa.com/js/key.html for a complete description. The short of |
@@ -51,8 +46,8 b' IPython.keyboard = (function (IPython) {' | |||
|
51 | 46 | '; :': 186, '= +': 187, '- _': 189 |
|
52 | 47 | }; |
|
53 | 48 | |
|
54 |
var browser = |
|
|
55 |
var platform = |
|
|
49 | var browser = utils.browser[0]; | |
|
50 | var platform = utils.platform; | |
|
56 | 51 | |
|
57 | 52 | if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') { |
|
58 | 53 | $.extend(_keycodes, _mozilla_keycodes); |
@@ -130,18 +125,19 b' IPython.keyboard = (function (IPython) {' | |||
|
130 | 125 | |
|
131 | 126 | // Shortcut manager class |
|
132 | 127 | |
|
133 | var ShortcutManager = function (delay) { | |
|
128 | var ShortcutManager = function (delay, events) { | |
|
134 | 129 | this._shortcuts = {}; |
|
135 | 130 | this._counts = {}; |
|
136 | 131 | this._timers = {}; |
|
137 | 132 | this.delay = delay || 800; // delay in milliseconds |
|
133 | this.events = events; | |
|
138 | 134 | }; |
|
139 | 135 | |
|
140 | 136 | ShortcutManager.prototype.help = function () { |
|
141 | 137 | var help = []; |
|
142 | 138 | for (var shortcut in this._shortcuts) { |
|
143 |
var help_string = this._shortcuts[shortcut] |
|
|
144 |
var help_index = this._shortcuts[shortcut] |
|
|
139 | var help_string = this._shortcuts[shortcut].help; | |
|
140 | var help_index = this._shortcuts[shortcut].help_index; | |
|
145 | 141 | if (help_string) { |
|
146 | 142 | if (platform === 'MacOS') { |
|
147 | 143 | shortcut = shortcut.replace('meta', 'cmd'); |
@@ -182,7 +178,7 b' IPython.keyboard = (function (IPython) {' | |||
|
182 | 178 | this._shortcuts[shortcut] = data; |
|
183 | 179 | if (!suppress_help_update) { |
|
184 | 180 | // update the keyboard shortcuts notebook help |
|
185 |
|
|
|
181 | this.events.trigger('rebuild.QuickHelp'); | |
|
186 | 182 | } |
|
187 | 183 | }; |
|
188 | 184 | |
@@ -191,7 +187,7 b' IPython.keyboard = (function (IPython) {' | |||
|
191 | 187 | this.add_shortcut(shortcut, data[shortcut], true); |
|
192 | 188 | } |
|
193 | 189 | // update the keyboard shortcuts notebook help |
|
194 |
|
|
|
190 | this.events.trigger('rebuild.QuickHelp'); | |
|
195 | 191 | }; |
|
196 | 192 | |
|
197 | 193 | ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) { |
@@ -200,7 +196,7 b' IPython.keyboard = (function (IPython) {' | |||
|
200 | 196 | delete this._shortcuts[shortcut]; |
|
201 | 197 | if (!suppress_help_update) { |
|
202 | 198 | // update the keyboard shortcuts notebook help |
|
203 |
|
|
|
199 | this.events.trigger('rebuild.QuickHelp'); | |
|
204 | 200 | } |
|
205 | 201 | }; |
|
206 | 202 | |
@@ -211,7 +207,7 b' IPython.keyboard = (function (IPython) {' | |||
|
211 | 207 | var timer = null; |
|
212 | 208 | if (c[shortcut] === data.count-1) { |
|
213 | 209 | c[shortcut] = 0; |
|
214 |
|
|
|
210 | timer = t[shortcut]; | |
|
215 | 211 | if (timer) {clearTimeout(timer); delete t[shortcut];} |
|
216 | 212 | return data.handler(event); |
|
217 | 213 | } else { |
@@ -228,7 +224,7 b' IPython.keyboard = (function (IPython) {' | |||
|
228 | 224 | var shortcut = event_to_shortcut(event); |
|
229 | 225 | var data = this._shortcuts[shortcut]; |
|
230 | 226 | if (data) { |
|
231 |
var handler = data |
|
|
227 | var handler = data.handler; | |
|
232 | 228 | if (handler) { |
|
233 | 229 | if (data.count === 1) { |
|
234 | 230 | return handler(event); |
@@ -243,10 +239,10 b' IPython.keyboard = (function (IPython) {' | |||
|
243 | 239 | ShortcutManager.prototype.handles = function (event) { |
|
244 | 240 | var shortcut = event_to_shortcut(event); |
|
245 | 241 | var data = this._shortcuts[shortcut]; |
|
246 | return !( data === undefined || data.handler === undefined ) | |
|
247 | } | |
|
242 | return !( data === undefined || data.handler === undefined ); | |
|
243 | }; | |
|
248 | 244 | |
|
249 | return { | |
|
245 | var keyboard = { | |
|
250 | 246 | keycodes : keycodes, |
|
251 | 247 | inv_keycodes : inv_keycodes, |
|
252 | 248 | ShortcutManager : ShortcutManager, |
@@ -256,4 +252,8 b' IPython.keyboard = (function (IPython) {' | |||
|
256 | 252 | event_to_shortcut : event_to_shortcut |
|
257 | 253 | }; |
|
258 | 254 | |
|
259 | }(IPython)); | |
|
255 | // For backwards compatability. | |
|
256 | IPython.keyboard = keyboard; | |
|
257 | ||
|
258 | return keyboard; | |
|
259 | }); |
@@ -1,34 +1,8 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
7 | 3 | |
|
8 | 4 | var IPython = IPython || {}; |
|
9 | ||
|
5 | define([], function(){ | |
|
10 | 6 | IPython.version = "3.0.0-dev"; |
|
11 | ||
|
12 | IPython.namespace = function (ns_string) { | |
|
13 | "use strict"; | |
|
14 | ||
|
15 | var parts = ns_string.split('.'), | |
|
16 | parent = IPython, | |
|
17 | i; | |
|
18 | ||
|
19 | // String redundant leading global | |
|
20 | if (parts[0] === "IPython") { | |
|
21 | parts = parts.slice(1); | |
|
22 | } | |
|
23 | ||
|
24 | for (i=0; i<parts.length; i+=1) { | |
|
25 | // Create property if it doesn't exist | |
|
26 | if (typeof parent[parts[i]] === "undefined") { | |
|
27 | parent[parts[i]] = {}; | |
|
28 | } | |
|
29 | } | |
|
30 | return parent; | |
|
31 | }; | |
|
32 | ||
|
33 | ||
|
34 | ||
|
7 | return IPython; | |
|
8 | }); |
@@ -1,32 +1,19 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
7 | 3 | |
|
8 | //============================================================================ | |
|
9 | // Global header/site setup. | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | var IPython = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | ], function(IPython, $){ | |
|
13 | 8 | "use strict"; |
|
14 | 9 | |
|
15 | 10 | var Page = function () { |
|
16 | this.style(); | |
|
17 | 11 | this.bind_events(); |
|
18 | 12 | }; |
|
19 | 13 | |
|
20 | Page.prototype.style = function () { | |
|
21 | $('div#header').addClass('border-box-sizing'); | |
|
22 | $('div#site').addClass('border-box-sizing'); | |
|
23 | }; | |
|
24 | ||
|
25 | ||
|
26 | 14 | Page.prototype.bind_events = function () { |
|
27 | 15 | }; |
|
28 | 16 | |
|
29 | ||
|
30 | 17 | Page.prototype.show = function () { |
|
31 | 18 | // The header and site divs start out hidden to prevent FLOUC. |
|
32 | 19 | // Main scripts should call this method after styling everything. |
@@ -34,23 +21,21 b' var IPython = (function (IPython) {' | |||
|
34 | 21 | this.show_site(); |
|
35 | 22 | }; |
|
36 | 23 | |
|
37 | ||
|
38 | 24 | Page.prototype.show_header = function () { |
|
39 | 25 | // The header and site divs start out hidden to prevent FLOUC. |
|
40 | 26 | // Main scripts should call this method after styling everything. |
|
27 | // TODO: selector are hardcoded, pass as constructor argument | |
|
41 | 28 | $('div#header').css('display','block'); |
|
42 | 29 | }; |
|
43 | 30 | |
|
44 | ||
|
45 | 31 | Page.prototype.show_site = function () { |
|
46 | 32 | // The header and site divs start out hidden to prevent FLOUC. |
|
47 | 33 | // Main scripts should call this method after styling everything. |
|
34 | // TODO: selector are hardcoded, pass as constructor argument | |
|
48 | 35 | $('div#site').css('display','block'); |
|
49 | 36 | }; |
|
50 | 37 | |
|
51 | ||
|
38 | // Register self in the global namespace for convenience. | |
|
52 | 39 | IPython.Page = Page; |
|
53 | ||
|
54 | return IPython; | |
|
55 | ||
|
56 | }(IPython)); | |
|
40 | return {'Page': Page}; | |
|
41 | }); |
@@ -1,20 +1,13 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2014 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // Utilities | |
|
10 | //============================================================================ | |
|
11 | IPython.namespace('IPython.security'); | |
|
12 | ||
|
13 | IPython.security = (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'components/google-caja/html-css-sanitizer-minified', | |
|
8 | ], function(IPython, $) { | |
|
14 | 9 | "use strict"; |
|
15 | 10 | |
|
16 | var utils = IPython.utils; | |
|
17 | ||
|
18 | 11 | var noop = function (x) { return x; }; |
|
19 | 12 | |
|
20 | 13 | var caja; |
@@ -117,10 +110,12 b' IPython.security = (function (IPython) {' | |||
|
117 | 110 | return sanitized; |
|
118 | 111 | }; |
|
119 | 112 | |
|
120 |
|
|
|
113 | var security = { | |
|
121 | 114 | caja: caja, |
|
122 | 115 | sanitize_html: sanitize_html |
|
123 | 116 | }; |
|
124 | 117 | |
|
125 | }(IPython)); | |
|
118 | IPython.security = security; | |
|
126 | 119 | |
|
120 | return security; | |
|
121 | }); |
@@ -1,13 +1,10 b'' | |||
|
1 | 1 | // Copyright (c) IPython Development Team. |
|
2 | 2 | // Distributed under the terms of the Modified BSD License. |
|
3 | 3 | |
|
4 | //============================================================================ | |
|
5 | // Utilities | |
|
6 | //============================================================================ | |
|
7 | ||
|
8 | IPython.namespace('IPython.utils'); | |
|
9 | ||
|
10 | IPython.utils = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | ], function(IPython, $){ | |
|
11 | 8 | "use strict"; |
|
12 | 9 | |
|
13 | 10 | IPython.load_extensions = function () { |
@@ -517,19 +514,24 b' IPython.utils = (function (IPython) {' | |||
|
517 | 514 | } |
|
518 | 515 | }; |
|
519 | 516 | |
|
517 | var ajax_error_msg = function (jqXHR) { | |
|
518 | // Return a JSON error message if there is one, | |
|
519 | // otherwise the basic HTTP status text. | |
|
520 | if (jqXHR.responseJSON && jqXHR.responseJSON.message) { | |
|
521 | return jqXHR.responseJSON.message; | |
|
522 | } else { | |
|
523 | return jqXHR.statusText; | |
|
524 | } | |
|
525 | } | |
|
520 | 526 | var log_ajax_error = function (jqXHR, status, error) { |
|
521 | 527 | // log ajax failures with informative messages |
|
522 | 528 | var msg = "API request failed (" + jqXHR.status + "): "; |
|
523 | 529 | console.log(jqXHR); |
|
524 | if (jqXHR.responseJSON && jqXHR.responseJSON.message) { | |
|
525 | msg += jqXHR.responseJSON.message; | |
|
526 | } else { | |
|
527 | msg += jqXHR.statusText; | |
|
528 | } | |
|
530 | msg += ajax_error_msg(jqXHR); | |
|
529 | 531 | console.log(msg); |
|
530 | 532 | }; |
|
531 | 533 | |
|
532 | return { | |
|
534 | var utils = { | |
|
533 | 535 | regex_split : regex_split, |
|
534 | 536 | uuid : uuid, |
|
535 | 537 | fixConsole : fixConsole, |
@@ -550,8 +552,12 b' IPython.utils = (function (IPython) {' | |||
|
550 | 552 | platform: platform, |
|
551 | 553 | is_or_has : is_or_has, |
|
552 | 554 | is_focused : is_focused, |
|
555 | ajax_error_msg : ajax_error_msg, | |
|
553 | 556 | log_ajax_error : log_ajax_error, |
|
554 | 557 | }; |
|
555 | 558 | |
|
556 | }(IPython)); | |
|
559 | // Backwards compatability. | |
|
560 | IPython.utils = utils; | |
|
557 | 561 | |
|
562 | return utils; | |
|
563 | }); |
@@ -184,6 +184,30 b' Browsers not listed, including Safari, are supported via the styling under the' | |||
|
184 | 184 | justify-content: center; |
|
185 | 185 | } |
|
186 | 186 | |
|
187 | .hbox.baseline, | |
|
188 | .vbox.baseline, | |
|
189 | .baseline { | |
|
190 | /* Old browsers */ | |
|
191 | -webkit-box-pack: baseline; | |
|
192 | -moz-box-pack: baseline; | |
|
193 | box-pack: baseline; | |
|
194 | ||
|
195 | /* Modern browsers */ | |
|
196 | justify-content: baseline; | |
|
197 | } | |
|
198 | ||
|
199 | .hbox.stretch, | |
|
200 | .vbox.stretch, | |
|
201 | .stretch { | |
|
202 | /* Old browsers */ | |
|
203 | -webkit-box-pack: stretch; | |
|
204 | -moz-box-pack: stretch; | |
|
205 | box-pack: stretch; | |
|
206 | ||
|
207 | /* Modern browsers */ | |
|
208 | justify-content: stretch; | |
|
209 | } | |
|
210 | ||
|
187 | 211 | .hbox.align-start, |
|
188 | 212 | .vbox.align-start, |
|
189 | 213 | .align-start { |
@@ -219,3 +243,27 b' Browsers not listed, including Safari, are supported via the styling under the' | |||
|
219 | 243 | /* Modern browsers */ |
|
220 | 244 | align-items: center; |
|
221 | 245 | } |
|
246 | ||
|
247 | .hbox.align-baseline, | |
|
248 | .vbox.align-baseline, | |
|
249 | .align-baseline { | |
|
250 | /* Old browsers */ | |
|
251 | -webkit-box-align: baseline; | |
|
252 | -moz-box-align: baseline; | |
|
253 | box-align: baseline; | |
|
254 | ||
|
255 | /* Modern browsers */ | |
|
256 | align-items: baseline; | |
|
257 | } | |
|
258 | ||
|
259 | .hbox.align-stretch, | |
|
260 | .vbox.align-stretch, | |
|
261 | .align-stretch { | |
|
262 | /* Old browsers */ | |
|
263 | -webkit-box-align: stretch; | |
|
264 | -moz-box-align: stretch; | |
|
265 | box-align: stretch; | |
|
266 | ||
|
267 | /* Modern browsers */ | |
|
268 | align-items: stretch; | |
|
269 | } |
@@ -24,6 +24,7 b' div#header {' | |||
|
24 | 24 | padding-left: 30px; |
|
25 | 25 | padding-bottom: 5px; |
|
26 | 26 | border-bottom: 1px solid @navbar-default-border; |
|
27 | .border-box-sizing(); | |
|
27 | 28 | } |
|
28 | 29 | |
|
29 | 30 | #ipython_notebook { |
@@ -50,6 +51,7 b' div#header {' | |||
|
50 | 51 | #site { |
|
51 | 52 | width: 100%; |
|
52 | 53 | display: none; |
|
54 | .border-box-sizing(); | |
|
53 | 55 | } |
|
54 | 56 | |
|
55 | 57 | /* Smaller buttons */ |
@@ -69,6 +71,14 b' span#login_widget {' | |||
|
69 | 71 | float: right; |
|
70 | 72 | } |
|
71 | 73 | |
|
74 | span#login_widget > .button, | |
|
75 | #logout | |
|
76 | { | |
|
77 | .btn(); | |
|
78 | .btn-default(); | |
|
79 | .btn-sm(); | |
|
80 | } | |
|
81 | ||
|
72 | 82 | .nav-header { |
|
73 | 83 | text-transform: none; |
|
74 | 84 | } |
@@ -93,3 +103,8 b' span#login_widget {' | |||
|
93 | 103 | } |
|
94 | 104 | } |
|
95 | 105 | |
|
106 | // less mixin to be sure to add the right class to get icons with font awesome. | |
|
107 | .icon(@ico){ | |
|
108 | .fa(); | |
|
109 | content: @ico; | |
|
110 | } |
@@ -1,3 +1,8 b'' | |||
|
1 | /*! | |
|
2 | * | |
|
3 | * IPython base | |
|
4 | * | |
|
5 | */ | |
|
1 | 6 | @import "variables.less"; |
|
2 | 7 | @import "mixins.less"; |
|
3 | 8 | @import "flexbox.less"; |
@@ -1,1 +1,1 b'' | |||
|
1 | Subproject commit 7c07f705d4f068828fb76f74ac7d4d355aea06f9 | |
|
1 | Subproject commit b3909af1b61ca7a412481759fdb441ecdfb3ab66 |
@@ -12,12 +12,12 b'' | |||
|
12 | 12 | * |
|
13 | 13 | * Same thing with `profile/static/custom/custom.css` to inject custom css into the notebook. |
|
14 | 14 | * |
|
15 | * Example : | |
|
15 | * __Example 1:__ | |
|
16 | 16 | * |
|
17 | 17 | * Create a custom button in toolbar that execute `%qtconsole` in kernel |
|
18 | 18 | * and hence open a qtconsole attached to the same kernel as the current notebook |
|
19 | 19 | * |
|
20 |
* |
|
|
20 | * IPython.events.on('app_initialized.NotebookApp', function(){ | |
|
21 | 21 | * IPython.toolbar.add_buttons_group([ |
|
22 | 22 | * { |
|
23 | 23 | * 'label' : 'run qtconsole', |
@@ -30,7 +30,16 b'' | |||
|
30 | 30 | * ]); |
|
31 | 31 | * }); |
|
32 | 32 | * |
|
33 | * Example : | |
|
33 | * __Example 2:__ | |
|
34 | * | |
|
35 | * At the completion of the dashboard loading, load an unofficial javascript extension | |
|
36 | * that is installed in profile/static/custom/ | |
|
37 | * | |
|
38 | * IPython.events.on('app_initialized.DashboardApp', function(){ | |
|
39 | * require(['custom/unofficial_extension.js']) | |
|
40 | * }); | |
|
41 | * | |
|
42 | * __Example 3:__ | |
|
34 | 43 | * |
|
35 | 44 | * Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );` |
|
36 | 45 | * to load custom script into the notebook. |
|
1 | NO CONTENT: modified file |
@@ -1,53 +1,61 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // Cell | |
|
10 | //============================================================================ | |
|
11 | /** | |
|
12 | * An extendable module that provide base functionnality to create cell for notebook. | |
|
13 | * @module IPython | |
|
14 | * @namespace IPython | |
|
15 | * @submodule Cell | |
|
16 | */ | |
|
17 | ||
|
18 | var IPython = (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | ], function(IPython, $, utils) { | |
|
9 | // TODO: remove IPython dependency here | |
|
19 | 10 | "use strict"; |
|
20 | 11 | |
|
21 | var utils = IPython.utils; | |
|
22 | var keycodes = IPython.keyboard.keycodes; | |
|
12 | // monkey patch CM to be able to syntax highlight cell magics | |
|
13 | // bug reported upstream, | |
|
14 | // see https://github.com/marijnh/CodeMirror2/issues/670 | |
|
15 | if(CodeMirror.getMode(1,'text/plain').indent === undefined ){ | |
|
16 | CodeMirror.modes.null = function() { | |
|
17 | return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}}; | |
|
18 | }; | |
|
19 | } | |
|
23 | 20 | |
|
24 | /** | |
|
25 | * The Base `Cell` class from which to inherit | |
|
26 | * @class Cell | |
|
27 | **/ | |
|
21 | CodeMirror.patchedGetMode = function(config, mode){ | |
|
22 | var cmmode = CodeMirror.getMode(config, mode); | |
|
23 | if(cmmode.indent === null) { | |
|
24 | console.log('patch mode "' , mode, '" on the fly'); | |
|
25 | cmmode.indent = function(){return 0;}; | |
|
26 | } | |
|
27 | return cmmode; | |
|
28 | }; | |
|
29 | // end monkey patching CodeMirror | |
|
28 | 30 | |
|
29 | /* | |
|
30 | * @constructor | |
|
31 | * | |
|
32 | * @param {object|undefined} [options] | |
|
33 | * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters | |
|
34 | */ | |
|
35 | 31 | var Cell = function (options) { |
|
36 | ||
|
37 | options = this.mergeopt(Cell, options); | |
|
32 | // Constructor | |
|
33 | // | |
|
34 | // The Base `Cell` class from which to inherit. | |
|
35 | // | |
|
36 | // Parameters: | |
|
37 | // options: dictionary | |
|
38 | // Dictionary of keyword arguments. | |
|
39 | // events: $(Events) instance | |
|
40 | // config: dictionary | |
|
41 | // keyboard_manager: KeyboardManager instance | |
|
42 | options = options || {}; | |
|
43 | this.keyboard_manager = options.keyboard_manager; | |
|
44 | this.events = options.events; | |
|
45 | var config = this.mergeopt(Cell, options.config); | |
|
38 | 46 | // superclass default overwrite our default |
|
39 | 47 | |
|
40 |
this.placeholder = |
|
|
41 |
this.read_only = |
|
|
48 | this.placeholder = config.placeholder || ''; | |
|
49 | this.read_only = config.cm_config.readOnly; | |
|
42 | 50 | this.selected = false; |
|
43 | 51 | this.rendered = false; |
|
44 | 52 | this.mode = 'command'; |
|
45 | 53 | this.metadata = {}; |
|
46 | 54 | // load this from metadata later ? |
|
47 | 55 | this.user_highlight = 'auto'; |
|
48 |
this.cm_config = |
|
|
56 | this.cm_config = config.cm_config; | |
|
49 | 57 | this.cell_id = utils.uuid(); |
|
50 |
this._options = |
|
|
58 | this._options = config; | |
|
51 | 59 | |
|
52 | 60 | // For JS VM engines optimization, attributes should be all set (even |
|
53 | 61 | // to null) in the constructor, and if possible, if different subclass |
@@ -70,7 +78,12 b' var IPython = (function (IPython) {' | |||
|
70 | 78 | cm_config : { |
|
71 | 79 | indentUnit : 4, |
|
72 | 80 | readOnly: false, |
|
73 | theme: "default" | |
|
81 | theme: "default", | |
|
82 | extraKeys: { | |
|
83 | "Cmd-Right":"goLineRight", | |
|
84 | "End":"goLineRight", | |
|
85 | "Cmd-Left":"goLineLeft" | |
|
86 | } | |
|
74 | 87 | } |
|
75 | 88 | }; |
|
76 | 89 | |
@@ -127,27 +140,27 b' var IPython = (function (IPython) {' | |||
|
127 | 140 | // We trigger events so that Cell doesn't have to depend on Notebook. |
|
128 | 141 | that.element.click(function (event) { |
|
129 | 142 | if (!that.selected) { |
|
130 |
|
|
|
143 | that.events.trigger('select.Cell', {'cell':that}); | |
|
131 | 144 | } |
|
132 | 145 | }); |
|
133 | 146 | that.element.focusin(function (event) { |
|
134 | 147 | if (!that.selected) { |
|
135 |
|
|
|
148 | that.events.trigger('select.Cell', {'cell':that}); | |
|
136 | 149 | } |
|
137 | 150 | }); |
|
138 | 151 | if (this.code_mirror) { |
|
139 | 152 | this.code_mirror.on("change", function(cm, change) { |
|
140 |
|
|
|
153 | that.events.trigger("set_dirty.Notebook", {value: true}); | |
|
141 | 154 | }); |
|
142 | 155 | } |
|
143 | 156 | if (this.code_mirror) { |
|
144 | 157 | this.code_mirror.on('focus', function(cm, change) { |
|
145 |
|
|
|
158 | that.events.trigger('edit_mode.Cell', {cell: that}); | |
|
146 | 159 | }); |
|
147 | 160 | } |
|
148 | 161 | if (this.code_mirror) { |
|
149 | 162 | this.code_mirror.on('blur', function(cm, change) { |
|
150 |
|
|
|
163 | that.events.trigger('command_mode.Cell', {cell: that}); | |
|
151 | 164 | }); |
|
152 | 165 | } |
|
153 | 166 | }; |
@@ -165,8 +178,7 b' var IPython = (function (IPython) {' | |||
|
165 | 178 | * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise |
|
166 | 179 | */ |
|
167 | 180 | Cell.prototype.handle_codemirror_keyevent = function (editor, event) { |
|
168 | var that = this; | |
|
169 | var shortcuts = IPython.keyboard_manager.edit_shortcuts; | |
|
181 | var shortcuts = this.keyboard_manager.edit_shortcuts; | |
|
170 | 182 | |
|
171 | 183 | // if this is an edit_shortcuts shortcut, the global keyboard/shortcut |
|
172 | 184 | // manager will handle it |
@@ -544,9 +556,8 b' var IPython = (function (IPython) {' | |||
|
544 | 556 | this.code_mirror.setOption('mode', default_mode); |
|
545 | 557 | }; |
|
546 | 558 | |
|
559 | // Backwards compatibility. | |
|
547 | 560 | IPython.Cell = Cell; |
|
548 | 561 | |
|
549 | return IPython; | |
|
550 | ||
|
551 | }(IPython)); | |
|
552 | ||
|
562 | return {'Cell': Cell}; | |
|
563 | }); |
@@ -1,32 +1,28 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2012 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // CellToolbar | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | ||
|
13 | /** | |
|
14 | * A Module to control the per-cell toolbar. | |
|
15 | * @module IPython | |
|
16 | * @namespace IPython | |
|
17 | * @submodule CellToolbar | |
|
18 | */ | |
|
19 | var IPython = (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/events' | |
|
8 | ], function(IPython, $, events) { | |
|
20 | 9 | "use strict"; |
|
21 | 10 | |
|
22 | /** | |
|
23 |
|
|
|
24 | * @class CellToolbar | |
|
25 | * @param {The cell to attach the metadata UI to} cell | |
|
26 | */ | |
|
27 | var CellToolbar = function (cell) { | |
|
11 | var CellToolbar = function (options) { | |
|
12 | // Constructor | |
|
13 | // | |
|
14 | // Parameters: | |
|
15 | // options: dictionary | |
|
16 | // Dictionary of keyword arguments. | |
|
17 | // events: $(Events) instance | |
|
18 | // cell: Cell instance | |
|
19 | // notebook: Notebook instance | |
|
20 | // | |
|
21 | // TODO: This leaks, when cell are deleted | |
|
22 | // There is still a reference to each celltoolbars. | |
|
28 | 23 | CellToolbar._instances.push(this); |
|
29 | this.cell = cell; | |
|
24 | this.notebook = options.notebook; | |
|
25 | this.cell = options.cell; | |
|
30 | 26 | this.create_element(); |
|
31 | 27 | this.rebuild(); |
|
32 | 28 | return this; |
@@ -34,7 +30,7 b' var IPython = (function (IPython) {' | |||
|
34 | 30 | |
|
35 | 31 | |
|
36 | 32 | CellToolbar.prototype.create_element = function () { |
|
37 | this.inner_element = $('<div/>').addClass('celltoolbar') | |
|
33 | this.inner_element = $('<div/>').addClass('celltoolbar'); | |
|
38 | 34 | this.element = $('<div/>').addClass('ctb_hideshow') |
|
39 | 35 | .append(this.inner_element); |
|
40 | 36 | }; |
@@ -182,13 +178,14 b' var IPython = (function (IPython) {' | |||
|
182 | 178 | * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5']) |
|
183 | 179 | * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5']) |
|
184 | 180 | */ |
|
185 | CellToolbar.register_preset = function(name, preset_list) { | |
|
181 | CellToolbar.register_preset = function(name, preset_list, notebook) { | |
|
186 | 182 | CellToolbar._presets[name] = preset_list; |
|
187 |
|
|
|
183 | events.trigger('preset_added.CellToolbar', {name: name}); | |
|
188 | 184 | // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded. |
|
189 | 185 | // In that case, activate the preset if needed. |
|
190 |
if ( |
|
|
191 |
|
|
|
186 | if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){ | |
|
187 | CellToolbar.activate_preset(name); | |
|
188 | } | |
|
192 | 189 | }; |
|
193 | 190 | |
|
194 | 191 | |
@@ -229,7 +226,7 b' var IPython = (function (IPython) {' | |||
|
229 | 226 | CellToolbar.rebuild_all(); |
|
230 | 227 | } |
|
231 | 228 | |
|
232 |
|
|
|
229 | events.trigger('preset_activated.CellToolbar', {name: preset_name}); | |
|
233 | 230 | }; |
|
234 | 231 | |
|
235 | 232 | |
@@ -283,7 +280,7 b' var IPython = (function (IPython) {' | |||
|
283 | 280 | } |
|
284 | 281 | |
|
285 | 282 | // If there are no controls or the cell is a rendered TextCell hide the toolbar. |
|
286 |
if (!this.ui_controls_list.length || (this.cell |
|
|
283 | if (!this.ui_controls_list.length || (this.cell.cell_type != 'code' && this.cell.rendered)) { | |
|
287 | 284 | this.hide(); |
|
288 | 285 | } else { |
|
289 | 286 | this.show(); |
@@ -347,7 +344,7 b' var IPython = (function (IPython) {' | |||
|
347 | 344 | setter(cell, !v); |
|
348 | 345 | chkb.attr("checked", !v); |
|
349 | 346 | }); |
|
350 |
button_container.append($('< |
|
|
347 | button_container.append($('<span/>').append(lbl)); | |
|
351 | 348 | }; |
|
352 | 349 | }; |
|
353 | 350 | |
@@ -411,12 +408,12 b' var IPython = (function (IPython) {' | |||
|
411 | 408 | select.change(function(){ |
|
412 | 409 | setter(cell, select.val()); |
|
413 | 410 | }); |
|
414 |
button_container.append($('< |
|
|
411 | button_container.append($('<span/>').append(lbl).append(select)); | |
|
415 | 412 | }; |
|
416 | 413 | }; |
|
417 | 414 | |
|
418 | ||
|
415 | // Backwards compatability. | |
|
419 | 416 | IPython.CellToolbar = CellToolbar; |
|
420 | 417 | |
|
421 | return IPython; | |
|
422 | }(IPython)); | |
|
418 | return {'CellToolbar': CellToolbar}; | |
|
419 | }); |
@@ -1,31 +1,29 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2012 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // CellToolbar Default | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | /** | |
|
13 | * Example Use for the CellToolbar library | |
|
14 | */ | |
|
15 | // IIFE without asignement, we don't modifiy the IPython namespace | |
|
16 | (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'jquery', | |
|
6 | 'notebook/js/celltoolbar', | |
|
7 | 'base/js/dialog', | |
|
8 | ], function($, celltoolbar, dialog) { | |
|
17 | 9 | "use strict"; |
|
18 | 10 | |
|
19 |
var CellToolbar = |
|
|
11 | var CellToolbar = celltoolbar.CellToolbar; | |
|
20 | 12 | |
|
21 | 13 | var raw_edit = function(cell){ |
|
22 |
|
|
|
14 | dialog.edit_metadata({ | |
|
15 | md: cell.metadata, | |
|
16 | callback: function (md) { | |
|
23 | 17 | cell.metadata = md; |
|
18 | }, | |
|
19 | name: 'Cell', | |
|
20 | notebook: this.notebook, | |
|
21 | keyboard_manager: this.keyboard_manager | |
|
24 | 22 | }); |
|
25 | 23 | }; |
|
26 | 24 | |
|
27 | 25 | var add_raw_edit_button = function(div, cell) { |
|
28 | var button_container = div; | |
|
26 | var button_container = $(div); | |
|
29 | 27 | var button = $('<button/>') |
|
30 | 28 | .addClass("btn btn-default btn-xs") |
|
31 | 29 | .text("Edit Metadata") |
@@ -36,11 +34,18 b'' | |||
|
36 | 34 | button_container.append(button); |
|
37 | 35 | }; |
|
38 | 36 | |
|
37 | var register = function (notebook) { | |
|
39 | 38 | CellToolbar.register_callback('default.rawedit', add_raw_edit_button); |
|
39 | raw_edit = $.proxy(raw_edit, { | |
|
40 | notebook: notebook, | |
|
41 | keyboard_manager: notebook.keyboard_manager | |
|
42 | }); | |
|
43 | ||
|
40 | 44 | var example_preset = []; |
|
41 | 45 | example_preset.push('default.rawedit'); |
|
42 | 46 | |
|
43 | CellToolbar.register_preset('Edit Metadata', example_preset); | |
|
47 | CellToolbar.register_preset('Edit Metadata', example_preset, notebook); | |
|
44 | 48 | console.log('Default extension for cell metadata editing loaded.'); |
|
45 | ||
|
46 | }(IPython)); | |
|
49 | }; | |
|
50 | return {'register': register}; | |
|
51 | }); |
@@ -1,28 +1,20 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2012 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // CellToolbar Example | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | /** | |
|
13 | * Example Use for the CellToolbar library | |
|
14 | * add the following to your custom.js to load | |
|
15 | * Celltoolbar UI for slideshow | |
|
16 | * | |
|
17 | * ``` | |
|
18 | * $.getScript('/static/js/celltoolbarpresets/example.js'); | |
|
19 | * ``` | |
|
20 | */ | |
|
21 | // IIFE without asignement, we don't modifiy the IPython namespace | |
|
22 | (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | // Example Use for the CellToolbar library | |
|
5 | // add the following to your custom.js to load | |
|
6 | // Celltoolbar UI for slideshow | |
|
7 | ||
|
8 | // ``` | |
|
9 | // $.getScript('/static/js/celltoolbarpresets/example.js'); | |
|
10 | // ``` | |
|
11 | define([ | |
|
12 | 'jquery', | |
|
13 | 'notebook/js/celltoolbar', | |
|
14 | ], function($, celltoolbar) { | |
|
23 | 15 | "use strict"; |
|
24 | 16 | |
|
25 |
var CellToolbar = |
|
|
17 | var CellToolbar = celltoolbar.CellToolbar; | |
|
26 | 18 | |
|
27 | 19 | var example_preset = []; |
|
28 | 20 | |
@@ -32,32 +24,32 b'' | |||
|
32 | 24 | var fun = function(value){ |
|
33 | 25 | try{ |
|
34 | 26 | if(value){ |
|
35 | cell.code_mirror.setOption('readOnly','nocursor') | |
|
36 | button.button('option','icons',{primary:'ui-icon-locked'}) | |
|
27 | cell.code_mirror.setOption('readOnly','nocursor'); | |
|
28 | button.button('option','icons',{primary:'ui-icon-locked'}); | |
|
37 | 29 | } else { |
|
38 | cell.code_mirror.setOption('readOnly',false) | |
|
39 | button.button('option','icons',{primary:'ui-icon-unlocked'}) | |
|
30 | cell.code_mirror.setOption('readOnly',false); | |
|
31 | button.button('option','icons',{primary:'ui-icon-unlocked'}); | |
|
40 | 32 | } |
|
41 | 33 | } catch(e){} |
|
42 | 34 | |
|
43 | } | |
|
44 | fun(cell.metadata.ro) | |
|
35 | }; | |
|
36 | fun(cell.metadata.ro); | |
|
45 | 37 | button.click(function(){ |
|
46 | 38 | var v = cell.metadata.ro; |
|
47 | 39 | var locked = !v; |
|
48 | 40 | cell.metadata.ro = locked; |
|
49 | fun(locked) | |
|
41 | fun(locked); | |
|
50 | 42 | }) |
|
51 | 43 | .css('height','16px') |
|
52 | 44 | .css('width','35px'); |
|
53 | 45 | button_container.append(button); |
|
54 | } | |
|
46 | }; | |
|
55 | 47 | |
|
56 | 48 | CellToolbar.register_callback('example.lock',simple_button); |
|
57 | 49 | example_preset.push('example.lock'); |
|
58 | 50 | |
|
59 | 51 | var toggle_test = function(div, cell) { |
|
60 | var button_container = $(div) | |
|
52 | var button_container = $(div); | |
|
61 | 53 | var button = $('<div/>') |
|
62 | 54 | .button({label:String(cell.metadata.foo)}). |
|
63 | 55 | css('width','65px'); |
@@ -65,9 +57,9 b'' | |||
|
65 | 57 | var v = cell.metadata.foo; |
|
66 | 58 | cell.metadata.foo = !v; |
|
67 | 59 | button.button("option","label",String(!v)); |
|
68 | }) | |
|
60 | }); | |
|
69 | 61 | button_container.append(button); |
|
70 | } | |
|
62 | }; | |
|
71 | 63 | |
|
72 | 64 | CellToolbar.register_callback('example.toggle',toggle_test); |
|
73 | 65 | example_preset.push('example.toggle'); |
@@ -76,16 +68,16 b'' | |||
|
76 | 68 | // setter |
|
77 | 69 | function(cell, value){ |
|
78 | 70 | // we check that the slideshow namespace exist and create it if needed |
|
79 | if (cell.metadata.yn_test == undefined){cell.metadata.yn_test = {}} | |
|
71 | if (cell.metadata.yn_test === undefined){cell.metadata.yn_test = {};} | |
|
80 | 72 | // set the value |
|
81 | cell.metadata.yn_test.value = value | |
|
73 | cell.metadata.yn_test.value = value; | |
|
82 | 74 | }, |
|
83 | 75 | //geter |
|
84 | 76 | function(cell){ var ns = cell.metadata.yn_test; |
|
85 | 77 | // if the slideshow namespace does not exist return `undefined` |
|
86 | 78 | // (will be interpreted as `false` by checkbox) otherwise |
|
87 | 79 | // return the value |
|
88 | return (ns == undefined)? undefined: ns.value | |
|
80 | return (ns === undefined)? undefined: ns.value; | |
|
89 | 81 | } |
|
90 | 82 | ); |
|
91 | 83 | |
@@ -103,16 +95,16 b'' | |||
|
103 | 95 | // setter |
|
104 | 96 | function(cell,value){ |
|
105 | 97 | // we check that the slideshow namespace exist and create it if needed |
|
106 | if (cell.metadata.test == undefined){cell.metadata.test = {}} | |
|
98 | if (cell.metadata.test === undefined){cell.metadata.test = {};} | |
|
107 | 99 | // set the value |
|
108 | cell.metadata.test.slide_type = value | |
|
100 | cell.metadata.test.slide_type = value; | |
|
109 | 101 | }, |
|
110 | 102 | //geter |
|
111 | 103 | function(cell){ var ns = cell.metadata.test; |
|
112 | 104 | // if the slideshow namespace does not exist return `undefined` |
|
113 | 105 | // (will be interpreted as `false` by checkbox) otherwise |
|
114 | 106 | // return the value |
|
115 | return (ns == undefined)? undefined: ns.slide_type | |
|
107 | return (ns === undefined)? undefined: ns.slide_type; | |
|
116 | 108 | }); |
|
117 | 109 | |
|
118 | 110 | CellToolbar.register_callback('example.select',select_test); |
@@ -120,7 +112,7 b'' | |||
|
120 | 112 | |
|
121 | 113 | var simple_dialog = function(title,text){ |
|
122 | 114 | var dlg = $('<div/>').attr('title',title) |
|
123 | .append($('<p/>').text(text)) | |
|
115 | .append($('<p/>').text(text)); | |
|
124 | 116 | $(dlg).dialog({ |
|
125 | 117 | autoOpen: true, |
|
126 | 118 | height: 300, |
@@ -131,24 +123,26 b'' | |||
|
131 | 123 | $(this).remove(); |
|
132 | 124 | } |
|
133 | 125 | }); |
|
134 | } | |
|
126 | }; | |
|
135 | 127 | |
|
136 | 128 | var add_simple_dialog_button = function(div, cell) { |
|
137 | 129 | var help_text = ["This is the Metadata editting UI.", |
|
138 | 130 | "It heavily rely on plugin to work ", |
|
139 | 131 | "and is still under developpement. You shouldn't wait too long before", |
|
140 | 132 | " seeing some customisable buttons in those toolbar." |
|
141 | ].join('\n') | |
|
142 | var button_container = $(div) | |
|
133 | ].join('\n'); | |
|
134 | var button_container = $(div); | |
|
143 | 135 | var button = $('<div/>').button({label:'?'}) |
|
144 | .click(function(){simple_dialog('help',help_text); return false;}) | |
|
136 | .click(function(){simple_dialog('help',help_text); return false;}); | |
|
145 | 137 | button_container.append(button); |
|
146 | } | |
|
138 | }; | |
|
147 | 139 | |
|
148 | CellToolbar.register_callback('example.help',add_simple_dialog_button) | |
|
149 | example_preset.push('example.help') | |
|
140 | var register = function (notebook) { | |
|
141 | CellToolbar.register_callback('example.help',add_simple_dialog_button); | |
|
142 | example_preset.push('example.help'); | |
|
150 | 143 | |
|
151 | CellToolbar.register_preset('Example',example_preset); | |
|
144 | CellToolbar.register_preset('Example',example_preset, notebook); | |
|
152 | 145 | console.log('Example extension for metadata editing loaded.'); |
|
153 | ||
|
154 | }(IPython)); | |
|
146 | }; | |
|
147 | return {'register': register}; | |
|
148 | }); |
@@ -1,18 +1,15 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2012 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
7 | 3 | |
|
8 | //============================================================================ | |
|
9 | // CellToolbar Example | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | (function(IPython) { | |
|
4 | define([ | |
|
5 | 'jquery', | |
|
6 | 'notebook/js/celltoolbar', | |
|
7 | 'base/js/dialog', | |
|
8 | 'base/js/keyboard', | |
|
9 | ], function($, celltoolbar, dialog, keyboard) { | |
|
13 | 10 | "use strict"; |
|
14 | 11 | |
|
15 |
var CellToolbar = |
|
|
12 | var CellToolbar = celltoolbar.CellToolbar; | |
|
16 | 13 | var raw_cell_preset = []; |
|
17 | 14 | |
|
18 | 15 | var select_type = CellToolbar.utils.select_ui_generator([ |
@@ -39,7 +36,7 b'' | |||
|
39 | 36 | $('<input/>').attr('type','text').attr('size','25') |
|
40 | 37 | .val(cell.metadata.raw_mimetype || "-") |
|
41 | 38 | ); |
|
42 |
|
|
|
39 | dialog.modal({ | |
|
43 | 40 | title: "Raw Cell MIME Type", |
|
44 | 41 | body: dialog, |
|
45 | 42 | buttons : { |
@@ -57,7 +54,7 b'' | |||
|
57 | 54 | var that = $(this); |
|
58 | 55 | // Upon ENTER, click the OK button. |
|
59 | 56 | that.find('input[type="text"]').keydown(function (event, ui) { |
|
60 |
if (event.which === |
|
|
57 | if (event.which === keyboard.keycodes.enter) { | |
|
61 | 58 | that.find('.btn-primary').first().click(); |
|
62 | 59 | return false; |
|
63 | 60 | } |
@@ -77,11 +74,13 b'' | |||
|
77 | 74 | "Raw NBConvert Format" |
|
78 | 75 | ); |
|
79 | 76 | |
|
77 | var register = function (notebook) { | |
|
80 | 78 | CellToolbar.register_callback('raw_cell.select', select_type, ['raw']); |
|
81 | ||
|
82 | 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 | 82 | console.log('Raw Cell Format toolbar preset loaded.'); |
|
83 | }; | |
|
84 | return {'register': register}; | |
|
86 | 85 | |
|
87 | }(IPython)); | |
|
86 | }); |
@@ -1,19 +1,14 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2012 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
7 | 3 | |
|
8 | //============================================================================ | |
|
9 | //CellToolbar Example | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | // IIFE without asignement, we don't modifiy the IPython namespace | |
|
13 | (function (IPython) { | |
|
4 | define([ | |
|
5 | 'jquery', | |
|
6 | 'notebook/js/celltoolbar', | |
|
7 | ], function($, celltoolbar) { | |
|
14 | 8 | "use strict"; |
|
15 | 9 | |
|
16 | var CellToolbar = IPython.CellToolbar; | |
|
10 | ||
|
11 | var CellToolbar = celltoolbar.CellToolbar; | |
|
17 | 12 | var slideshow_preset = []; |
|
18 | 13 | |
|
19 | 14 | var select_type = CellToolbar.utils.select_ui_generator([ |
@@ -27,24 +22,25 b'' | |||
|
27 | 22 | // setter |
|
28 | 23 | function(cell, value){ |
|
29 | 24 | // we check that the slideshow namespace exist and create it if needed |
|
30 | if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}} | |
|
25 | if (cell.metadata.slideshow === undefined){cell.metadata.slideshow = {};} | |
|
31 | 26 | // set the value |
|
32 | cell.metadata.slideshow.slide_type = value | |
|
27 | cell.metadata.slideshow.slide_type = value; | |
|
33 | 28 | }, |
|
34 | 29 | //geter |
|
35 | 30 | function(cell){ var ns = cell.metadata.slideshow; |
|
36 | 31 | // if the slideshow namespace does not exist return `undefined` |
|
37 | 32 | // (will be interpreted as `false` by checkbox) otherwise |
|
38 | 33 | // return the value |
|
39 | return (ns == undefined)? undefined: ns.slide_type | |
|
34 | return (ns === undefined)? undefined: ns.slide_type; | |
|
40 | 35 | }, |
|
41 | 36 | "Slide Type"); |
|
42 | 37 | |
|
38 | var register = function (notebook) { | |
|
43 | 39 | CellToolbar.register_callback('slideshow.select',select_type); |
|
44 | ||
|
45 | 40 | slideshow_preset.push('slideshow.select'); |
|
46 | 41 | |
|
47 | CellToolbar.register_preset('Slideshow',slideshow_preset); | |
|
42 | CellToolbar.register_preset('Slideshow',slideshow_preset, notebook); | |
|
48 | 43 | console.log('Slideshow extension for metadata editing loaded.'); |
|
49 | ||
|
50 | }(IPython)); | |
|
44 | }; | |
|
45 | return {'register': register}; | |
|
46 | }); |
@@ -1,20 +1,18 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // CodeCell | |
|
10 | //============================================================================ | |
|
11 | /** | |
|
12 | * An extendable module that provide base functionnality to create cell for notebook. | |
|
13 | * @module IPython | |
|
14 | * @namespace IPython | |
|
15 | * @submodule CodeCell | |
|
16 | */ | |
|
17 | ||
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | 'base/js/keyboard', | |
|
9 | 'notebook/js/cell', | |
|
10 | 'notebook/js/outputarea', | |
|
11 | 'notebook/js/completer', | |
|
12 | 'notebook/js/celltoolbar', | |
|
13 | ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) { | |
|
14 | "use strict"; | |
|
15 | var Cell = cell.Cell; | |
|
18 | 16 | |
|
19 | 17 | /* local util for codemirror */ |
|
20 | 18 | var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}; |
@@ -40,29 +38,30 b' CodeMirror.commands.delSpaceToPrevTabStop = function(cm){' | |||
|
40 | 38 | } |
|
41 | 39 | }; |
|
42 | 40 | |
|
41 | var keycodes = keyboard.keycodes; | |
|
43 | 42 | |
|
44 | var IPython = (function (IPython) { | |
|
45 | "use strict"; | |
|
46 | ||
|
47 | var utils = IPython.utils; | |
|
48 | var keycodes = IPython.keyboard.keycodes; | |
|
49 | ||
|
50 | /** | |
|
51 | * A Cell conceived to write code. | |
|
52 | * | |
|
53 | * The kernel doesn't have to be set at creation time, in that case | |
|
54 | * it will be null and set_kernel has to be called later. | |
|
55 | * @class CodeCell | |
|
56 | * @extends IPython.Cell | |
|
57 | * | |
|
58 | * @constructor | |
|
59 | * @param {Object|null} kernel | |
|
60 | * @param {object|undefined} [options] | |
|
61 | * @param [options.cm_config] {object} config to pass to CodeMirror | |
|
62 | */ | |
|
63 | 43 | var CodeCell = function (kernel, options) { |
|
44 | // Constructor | |
|
45 | // | |
|
46 | // A Cell conceived to write code. | |
|
47 | // | |
|
48 | // Parameters: | |
|
49 | // kernel: Kernel instance | |
|
50 | // The kernel doesn't have to be set at creation time, in that case | |
|
51 | // it will be null and set_kernel has to be called later. | |
|
52 | // options: dictionary | |
|
53 | // Dictionary of keyword arguments. | |
|
54 | // events: $(Events) instance | |
|
55 | // config: dictionary | |
|
56 | // keyboard_manager: KeyboardManager instance | |
|
57 | // notebook: Notebook instance | |
|
58 | // tooltip: Tooltip instance | |
|
64 | 59 | this.kernel = kernel || null; |
|
60 | this.notebook = options.notebook; | |
|
65 | 61 | this.collapsed = false; |
|
62 | this.events = options.events; | |
|
63 | this.tooltip = options.tooltip; | |
|
64 | this.config = options.config; | |
|
66 | 65 | |
|
67 | 66 | // create all attributed in constructor function |
|
68 | 67 | // even if null for V8 VM optimisation |
@@ -77,9 +76,11 b' var IPython = (function (IPython) {' | |||
|
77 | 76 | onKeyEvent: $.proxy(this.handle_keyevent,this) |
|
78 | 77 | }; |
|
79 | 78 | |
|
80 |
|
|
|
81 | ||
|
82 | IPython.Cell.apply(this,[options]); | |
|
79 | var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options}); | |
|
80 | Cell.apply(this,[{ | |
|
81 | config: config, | |
|
82 | keyboard_manager: options.keyboard_manager, | |
|
83 | events: this.events}]); | |
|
83 | 84 | |
|
84 | 85 | // Attributes we want to override in this subclass. |
|
85 | 86 | this.cell_type = "code"; |
@@ -109,29 +110,31 b' var IPython = (function (IPython) {' | |||
|
109 | 110 | |
|
110 | 111 | CodeCell.msg_cells = {}; |
|
111 | 112 | |
|
112 |
CodeCell.prototype = new |
|
|
113 | CodeCell.prototype = new Cell(); | |
|
113 | 114 | |
|
114 | 115 | /** |
|
115 | 116 | * @method auto_highlight |
|
116 | 117 | */ |
|
117 | 118 | CodeCell.prototype.auto_highlight = function () { |
|
118 |
this._auto_highlight( |
|
|
119 | this._auto_highlight(this.config.cell_magic_highlight); | |
|
119 | 120 | }; |
|
120 | 121 | |
|
121 | 122 | /** @method create_element */ |
|
122 | 123 | CodeCell.prototype.create_element = function () { |
|
123 |
|
|
|
124 | Cell.prototype.create_element.apply(this, arguments); | |
|
124 | 125 | |
|
125 |
var cell = $('<div></div>').addClass('cell |
|
|
126 | var cell = $('<div></div>').addClass('cell code_cell'); | |
|
126 | 127 | cell.attr('tabindex','2'); |
|
127 | 128 | |
|
128 | 129 | var input = $('<div></div>').addClass('input'); |
|
129 | 130 | var prompt = $('<div/>').addClass('prompt input_prompt'); |
|
130 | 131 | var inner_cell = $('<div/>').addClass('inner_cell'); |
|
131 |
this.celltoolbar = new |
|
|
132 | this.celltoolbar = new celltoolbar.CellToolbar({ | |
|
133 | cell: this, | |
|
134 | notebook: this.notebook}); | |
|
132 | 135 | inner_cell.append(this.celltoolbar.element); |
|
133 | 136 | var input_area = $('<div/>').addClass('input_area'); |
|
134 | this.code_mirror = CodeMirror(input_area.get(0), this.cm_config); | |
|
137 | this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config); | |
|
135 | 138 | $(this.code_mirror.getInputField()).attr("spellcheck", "false"); |
|
136 | 139 | inner_cell.append(input_area); |
|
137 | 140 | input.append(prompt).append(inner_cell); |
@@ -158,13 +161,17 b' var IPython = (function (IPython) {' | |||
|
158 | 161 | var output = $('<div></div>'); |
|
159 | 162 | cell.append(input).append(widget_area).append(output); |
|
160 | 163 | this.element = cell; |
|
161 |
this.output_area = new |
|
|
162 | this.completer = new IPython.Completer(this); | |
|
164 | this.output_area = new outputarea.OutputArea({ | |
|
165 | selector: output, | |
|
166 | prompt_area: true, | |
|
167 | events: this.events, | |
|
168 | keyboard_manager: this.keyboard_manager}); | |
|
169 | this.completer = new completer.Completer(this, this.events); | |
|
163 | 170 | }; |
|
164 | 171 | |
|
165 | 172 | /** @method bind_events */ |
|
166 | 173 | CodeCell.prototype.bind_events = function () { |
|
167 |
|
|
|
174 | Cell.prototype.bind_events.apply(this); | |
|
168 | 175 | var that = this; |
|
169 | 176 | |
|
170 | 177 | this.element.focusout( |
@@ -187,7 +194,7 b' var IPython = (function (IPython) {' | |||
|
187 | 194 | // they are sent, and remove tooltip if any, except for tab again |
|
188 | 195 | var tooltip_closed = null; |
|
189 | 196 | if (event.type === 'keydown' && event.which != keycodes.tab ) { |
|
190 |
tooltip_closed = |
|
|
197 | tooltip_closed = this.tooltip.remove_and_cancel_tooltip(); | |
|
191 | 198 | } |
|
192 | 199 | |
|
193 | 200 | var cur = editor.getCursor(); |
@@ -195,21 +202,21 b' var IPython = (function (IPython) {' | |||
|
195 | 202 | this.auto_highlight(); |
|
196 | 203 | } |
|
197 | 204 | |
|
198 |
if (event.which === keycodes.down && event.type === 'keypress' && |
|
|
205 | if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) { | |
|
199 | 206 | // triger on keypress (!) otherwise inconsistent event.which depending on plateform |
|
200 | 207 | // browser and keyboard layout ! |
|
201 | 208 | // Pressing '(' , request tooltip, don't forget to reappend it |
|
202 | 209 | // The second argument says to hide the tooltip if the docstring |
|
203 | 210 | // is actually empty |
|
204 |
|
|
|
211 | this.tooltip.pending(that, true); | |
|
205 | 212 | } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') { |
|
206 | 213 | // If tooltip is active, cancel it. The call to |
|
207 | 214 | // remove_and_cancel_tooltip above doesn't pass, force=true. |
|
208 | 215 | // Because of this it won't actually close the tooltip |
|
209 | 216 | // if it is in sticky mode. Thus, we have to check again if it is open |
|
210 | 217 | // and close it with force=true. |
|
211 |
if (! |
|
|
212 |
|
|
|
218 | if (!this.tooltip._hidden) { | |
|
219 | this.tooltip.remove_and_cancel_tooltip(true); | |
|
213 | 220 | } |
|
214 | 221 | // If we closed the tooltip, don't let CM or the global handlers |
|
215 | 222 | // handle this event. |
@@ -223,12 +230,12 b' var IPython = (function (IPython) {' | |||
|
223 | 230 | return false; |
|
224 | 231 | } |
|
225 | 232 | } |
|
226 |
|
|
|
233 | this.tooltip.request(that); | |
|
227 | 234 | event.stop(); |
|
228 | 235 | return true; |
|
229 | 236 | } else if (event.keyCode === keycodes.tab && event.type == 'keydown') { |
|
230 | 237 | // Tab completion. |
|
231 |
|
|
|
238 | this.tooltip.remove_and_cancel_tooltip(); | |
|
232 | 239 | if (editor.somethingSelected()) { |
|
233 | 240 | return false; |
|
234 | 241 | } |
@@ -246,7 +253,7 b' var IPython = (function (IPython) {' | |||
|
246 | 253 | |
|
247 | 254 | // keyboard event wasn't one of those unique to code cells, let's see |
|
248 | 255 | // if it's one of the generic ones (i.e. check edit mode shortcuts) |
|
249 |
return |
|
|
256 | return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]); | |
|
250 | 257 | }; |
|
251 | 258 | |
|
252 | 259 | // Kernel related calls. |
@@ -305,7 +312,7 b' var IPython = (function (IPython) {' | |||
|
305 | 312 | }; |
|
306 | 313 | |
|
307 | 314 | CodeCell.prototype._open_with_pager = function (payload) { |
|
308 |
|
|
|
315 | this.events.trigger('open_with_text.Pager', payload); | |
|
309 | 316 | }; |
|
310 | 317 | |
|
311 | 318 | /** |
@@ -315,7 +322,7 b' var IPython = (function (IPython) {' | |||
|
315 | 322 | CodeCell.prototype._handle_execute_reply = function (msg) { |
|
316 | 323 | this.set_input_prompt(msg.content.execution_count); |
|
317 | 324 | this.element.removeClass("running"); |
|
318 |
|
|
|
325 | this.events.trigger('set_dirty.Notebook', {value: true}); | |
|
319 | 326 | }; |
|
320 | 327 | |
|
321 | 328 | /** |
@@ -324,7 +331,7 b' var IPython = (function (IPython) {' | |||
|
324 | 331 | */ |
|
325 | 332 | CodeCell.prototype._handle_set_next_input = function (payload) { |
|
326 | 333 | var data = {'cell': this, 'text': payload.text}; |
|
327 |
|
|
|
334 | this.events.trigger('set_next_input.Notebook', data); | |
|
328 | 335 | }; |
|
329 | 336 | |
|
330 | 337 | /** |
@@ -339,7 +346,7 b' var IPython = (function (IPython) {' | |||
|
339 | 346 | // Basic cell manipulation. |
|
340 | 347 | |
|
341 | 348 | CodeCell.prototype.select = function () { |
|
342 |
var cont = |
|
|
349 | var cont = Cell.prototype.select.apply(this); | |
|
343 | 350 | if (cont) { |
|
344 | 351 | this.code_mirror.refresh(); |
|
345 | 352 | this.auto_highlight(); |
@@ -348,7 +355,7 b' var IPython = (function (IPython) {' | |||
|
348 | 355 | }; |
|
349 | 356 | |
|
350 | 357 | CodeCell.prototype.render = function () { |
|
351 |
var cont = |
|
|
358 | var cont = Cell.prototype.render.apply(this); | |
|
352 | 359 | // Always execute, even if we are already in the rendered state |
|
353 | 360 | return cont; |
|
354 | 361 | }; |
@@ -451,7 +458,7 b' var IPython = (function (IPython) {' | |||
|
451 | 458 | // JSON serialization |
|
452 | 459 | |
|
453 | 460 | CodeCell.prototype.fromJSON = function (data) { |
|
454 |
|
|
|
461 | Cell.prototype.fromJSON.apply(this, arguments); | |
|
455 | 462 | if (data.cell_type === 'code') { |
|
456 | 463 | if (data.input !== undefined) { |
|
457 | 464 | this.set_text(data.input); |
@@ -479,7 +486,7 b' var IPython = (function (IPython) {' | |||
|
479 | 486 | |
|
480 | 487 | |
|
481 | 488 | CodeCell.prototype.toJSON = function () { |
|
482 |
var data = |
|
|
489 | var data = Cell.prototype.toJSON.apply(this); | |
|
483 | 490 | data.input = this.get_text(); |
|
484 | 491 | // is finite protect against undefined and '*' value |
|
485 | 492 | if (isFinite(this.input_prompt_number)) { |
@@ -499,11 +506,11 b' var IPython = (function (IPython) {' | |||
|
499 | 506 | * @return is the action being taken |
|
500 | 507 | */ |
|
501 | 508 | CodeCell.prototype.unselect = function () { |
|
502 |
var cont = |
|
|
509 | var cont = Cell.prototype.unselect.apply(this); | |
|
503 | 510 | if (cont) { |
|
504 | 511 | // When a code cell is usnelected, make sure that the corresponding |
|
505 | 512 | // tooltip and completer to that cell is closed. |
|
506 |
|
|
|
513 | this.tooltip.remove_and_cancel_tooltip(true); | |
|
507 | 514 | if (this.completer !== null) { |
|
508 | 515 | this.completer.close(); |
|
509 | 516 | } |
@@ -511,7 +518,8 b' var IPython = (function (IPython) {' | |||
|
511 | 518 | return cont; |
|
512 | 519 | }; |
|
513 | 520 | |
|
521 | // Backwards compatability. | |
|
514 | 522 | IPython.CodeCell = CodeCell; |
|
515 | 523 | |
|
516 | return IPython; | |
|
517 | }(IPython)); | |
|
524 | return {'CodeCell': CodeCell}; | |
|
525 | }); |
@@ -7,10 +7,15 b" CodeMirror.requireMode('python',function(){" | |||
|
7 | 7 | "use strict"; |
|
8 | 8 | |
|
9 | 9 | CodeMirror.defineMode("ipython", function(conf, parserConf) { |
|
10 | ||
|
11 | parserConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]"); | |
|
12 | parserConf.name = 'python' | |
|
13 | return CodeMirror.getMode(conf, parserConf); | |
|
10 | var pythonConf = {}; | |
|
11 | for (var prop in parserConf) { | |
|
12 | if (parserConf.hasOwnProperty(prop)) { | |
|
13 | pythonConf[prop] = parserConf[prop]; | |
|
14 | } | |
|
15 | } | |
|
16 | pythonConf.name = 'python'; | |
|
17 | pythonConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]"); | |
|
18 | return CodeMirror.getMode(conf, pythonConf); | |
|
14 | 19 | }, 'python'); |
|
15 | 20 | |
|
16 | 21 | CodeMirror.defineMIME("text/x-ipython", "ipython"); |
@@ -8,7 +8,6 b'' | |||
|
8 | 8 | |
|
9 | 9 | CodeMirror.requireMode('gfm', function(){ |
|
10 | 10 | CodeMirror.requireMode('stex', function(){ |
|
11 | console.log('defining custom mode...'); | |
|
12 | 11 | CodeMirror.defineMode("ipythongfm", function(config, parserConfig) { |
|
13 | 12 | |
|
14 | 13 | var gfm_mode = CodeMirror.getMode(config, "gfm"); |
@@ -1,17 +1,17 b'' | |||
|
1 | 1 | // Copyright (c) IPython Development Team. |
|
2 | 2 | // Distributed under the terms of the Modified BSD License. |
|
3 | 3 | |
|
4 | // Completer | |
|
5 | // | |
|
6 | // Completer is be a class that takes a cell instance. | |
|
7 | ||
|
8 | var IPython = (function (IPython) { | |
|
9 | // that will prevent us from misspelling | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | 'base/js/keyboard', | |
|
9 | 'notebook/js/contexthint', | |
|
10 | ], function(IPython, $, utils, keyboard) { | |
|
10 | 11 | "use strict"; |
|
11 | 12 | |
|
12 | 13 | // easier key mapping |
|
13 |
var keycodes = |
|
|
14 | var utils = IPython.utils; | |
|
14 | var keycodes = keyboard.keycodes; | |
|
15 | 15 | |
|
16 | 16 | var prepend_n_prc = function(str, n) { |
|
17 | 17 | for( var i =0 ; i< n ; i++){ |
@@ -78,14 +78,14 b' var IPython = (function (IPython) {' | |||
|
78 | 78 | } |
|
79 | 79 | |
|
80 | 80 | |
|
81 | var Completer = function (cell) { | |
|
81 | var Completer = function (cell, events) { | |
|
82 | 82 | this.cell = cell; |
|
83 | 83 | this.editor = cell.code_mirror; |
|
84 | 84 | var that = this; |
|
85 |
|
|
|
85 | events.on('status_busy.Kernel', function () { | |
|
86 | 86 | that.skip_kernel_completion = true; |
|
87 | 87 | }); |
|
88 |
|
|
|
88 | events.on('status_idle.Kernel', function () { | |
|
89 | 89 | that.skip_kernel_completion = false; |
|
90 | 90 | }); |
|
91 | 91 | }; |
@@ -351,6 +351,18 b' var IPython = (function (IPython) {' | |||
|
351 | 351 | } |
|
352 | 352 | index = Math.min(Math.max(index, 0), options.length-1); |
|
353 | 353 | this.sel[0].selectedIndex = index; |
|
354 | } else if (code == keycodes.pageup || code == keycodes.pagedown) { | |
|
355 | CodeMirror.e_stop(event); | |
|
356 | ||
|
357 | var options = this.sel.find('option'); | |
|
358 | var index = this.sel[0].selectedIndex; | |
|
359 | if (code == keycodes.pageup) { | |
|
360 | index -= 10; // As 10 is the hard coded size of the drop down menu | |
|
361 | } else { | |
|
362 | index += 10; | |
|
363 | } | |
|
364 | index = Math.min(Math.max(index, 0), options.length-1); | |
|
365 | this.sel[0].selectedIndex = index; | |
|
354 | 366 | } else if (code == keycodes.left || code == keycodes.right) { |
|
355 | 367 | this.close(); |
|
356 | 368 | } |
@@ -379,7 +391,9 b' var IPython = (function (IPython) {' | |||
|
379 | 391 | that.carry_on_completion(); |
|
380 | 392 | }, 50); |
|
381 | 393 | }; |
|
394 | ||
|
395 | // For backwards compatability. | |
|
382 | 396 | IPython.Completer = Completer; |
|
383 | 397 | |
|
384 | return IPython; | |
|
385 | }(IPython)); | |
|
398 | return {'Completer': Completer}; | |
|
399 | }); |
@@ -1,28 +1,9 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2012 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
7 | 3 | |
|
8 | //============================================================================ | |
|
9 | // Notebook | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | /** | |
|
13 | * @module IPython | |
|
14 | * @namespace IPython | |
|
15 | **/ | |
|
16 | ||
|
17 | var IPython = (function (IPython) { | |
|
4 | define([], function() { | |
|
18 | 5 | "use strict"; |
|
19 | /** | |
|
20 | * A place where some stuff can be confugured. | |
|
21 | * | |
|
22 | * @class config | |
|
23 | * @static | |
|
24 | * | |
|
25 | **/ | |
|
6 | ||
|
26 | 7 | var default_config = { |
|
27 | 8 | /** |
|
28 | 9 | * Dictionary of object to autodetect highlight mode for code cell. |
@@ -50,13 +31,13 b' var IPython = (function (IPython) {' | |||
|
50 | 31 | * cell_magic_highlight['javascript'] = {'reg':[/^var/]} |
|
51 | 32 | */ |
|
52 | 33 | cell_magic_highlight : { |
|
53 |
|
|
|
54 |
|
|
|
55 |
|
|
|
56 |
|
|
|
57 |
|
|
|
58 |
|
|
|
59 |
|
|
|
34 | 'magic_javascript' :{'reg':[/^%%javascript/]}, | |
|
35 | 'magic_perl' :{'reg':[/^%%perl/]}, | |
|
36 | 'magic_ruby' :{'reg':[/^%%ruby/]}, | |
|
37 | 'magic_python' :{'reg':[/^%%python3?/]}, | |
|
38 | 'magic_shell' :{'reg':[/^%%bash/]}, | |
|
39 | 'magic_r' :{'reg':[/^%%R/]}, | |
|
40 | 'magic_text/x-cython' :{'reg':[/^%%cython/]}, | |
|
60 | 41 |
|
|
61 | 42 | |
|
62 | 43 | /** |
@@ -66,14 +47,9 b' var IPython = (function (IPython) {' | |||
|
66 | 47 | raw_cell_highlight : { |
|
67 | 48 |
|
|
68 | 49 |
|
|
69 | ||
|
70 | 50 |
|
|
71 | 51 | |
|
72 | // use the same method to merge user configuration | |
|
73 | IPython.config = {}; | |
|
74 | $.extend(IPython.config, default_config); | |
|
75 | ||
|
76 | return IPython; | |
|
77 | ||
|
78 | }(IPython)); | |
|
79 | ||
|
52 | return { | |
|
53 | 'default_config': default_config, | |
|
54 | }; | |
|
55 | }); |
@@ -1,12 +1,15 b'' | |||
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
1 | 4 | // highly adapted for codemiror jshint |
|
2 |
(function |
|
|
5 | define([], function() { | |
|
3 | 6 | "use strict"; |
|
4 | 7 | |
|
5 |
|
|
|
8 | var forEach = function(arr, f) { | |
|
6 | 9 | for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); |
|
7 | } | |
|
10 | }; | |
|
8 | 11 | |
|
9 |
|
|
|
12 | var arrayContains = function(arr, item) { | |
|
10 | 13 | if (!Array.prototype.indexOf) { |
|
11 | 14 | var i = arr.length; |
|
12 | 15 | while (i--) { |
@@ -17,7 +20,7 b'' | |||
|
17 | 20 | return false; |
|
18 | 21 | } |
|
19 | 22 | return arr.indexOf(item) != -1; |
|
20 | } | |
|
23 | }; | |
|
21 | 24 | |
|
22 | 25 | CodeMirror.contextHint = function (editor) { |
|
23 | 26 | // Find the token at the cursor |
@@ -26,7 +29,7 b'' | |||
|
26 | 29 | tprop = token; |
|
27 | 30 | // If it's not a 'word-style' token, ignore the token. |
|
28 | 31 | // If it is a property, find out what it is a property of. |
|
29 |
var list = |
|
|
32 | var list = []; | |
|
30 | 33 | var clist = getCompletions(token, editor); |
|
31 | 34 | for (var i = 0; i < clist.length; i++) { |
|
32 | 35 | list.push({ |
@@ -40,10 +43,10 b'' | |||
|
40 | 43 | line: cur.line, |
|
41 | 44 | ch: token.end |
|
42 | 45 | } |
|
43 | }) | |
|
46 | }); | |
|
44 | 47 | } |
|
45 | 48 | return list; |
|
46 | } | |
|
49 | }; | |
|
47 | 50 | |
|
48 | 51 | // find all 'words' of current cell |
|
49 | 52 | var getAllTokens = function (editor) { |
@@ -71,7 +74,7 b'' | |||
|
71 | 74 |
|
|
72 | 75 |
|
|
73 | 76 |
|
|
74 |
|
|
|
77 | if (tk.className !== null) { | |
|
75 | 78 |
|
|
76 | 79 |
|
|
77 | 80 |
|
@@ -79,16 +82,17 b'' | |||
|
79 | 82 |
|
|
80 | 83 |
|
|
81 | 84 |
|
|
82 |
|
|
|
83 | ||
|
85 | }; | |
|
84 | 86 | |
|
85 |
|
|
|
87 | var getCompletions = function(token, editor) { | |
|
86 | 88 | var candidates = getAllTokens(editor); |
|
87 | 89 | // filter all token that have a common start (but nox exactly) the lenght of the current token |
|
88 | 90 | var lambda = function (x) { |
|
89 | return (x.indexOf(token.string) == 0 && x != token.string) | |
|
91 | return (x.indexOf(token.string) === 0 && x != token.string); | |
|
90 | 92 | }; |
|
91 | 93 | var filterd = candidates.filter(lambda); |
|
92 | 94 | return filterd; |
|
93 | } | |
|
94 | })(); | |
|
95 | }; | |
|
96 | ||
|
97 | return {'contextHint': CodeMirror.contextHint}; | |
|
98 | }); |
@@ -1,23 +1,45 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // Keyboard management | |
|
10 | //============================================================================ | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
11 | 3 | |
|
12 | var IPython = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | 'base/js/keyboard', | |
|
9 | ], function(IPython, $, utils, keyboard) { | |
|
13 | 10 | "use strict"; |
|
14 | 11 | |
|
15 |
var browser = |
|
|
16 |
var platform = |
|
|
12 | var browser = utils.browser[0]; | |
|
13 | var platform = utils.platform; | |
|
14 | ||
|
15 | // Main keyboard manager for the notebook | |
|
16 | var keycodes = keyboard.keycodes; | |
|
17 | 17 | |
|
18 | // Default keyboard shortcuts | |
|
18 | var KeyboardManager = function (options) { | |
|
19 | // Constructor | |
|
20 | // | |
|
21 | // Parameters: | |
|
22 | // options: dictionary | |
|
23 | // Dictionary of keyword arguments. | |
|
24 | // events: $(Events) instance | |
|
25 | // pager: Pager instance | |
|
26 | this.mode = 'command'; | |
|
27 | this.enabled = true; | |
|
28 | this.pager = options.pager; | |
|
29 | this.quick_help = undefined; | |
|
30 | this.notebook = undefined; | |
|
31 | this.bind_events(); | |
|
32 | this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events); | |
|
33 | this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts()); | |
|
34 | this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts()); | |
|
35 | this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events); | |
|
36 | this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts()); | |
|
37 | this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts()); | |
|
38 | }; | |
|
19 | 39 | |
|
20 |
|
|
|
40 | KeyboardManager.prototype.get_default_common_shortcuts = function() { | |
|
41 | var that = this; | |
|
42 | var shortcuts = { | |
|
21 | 43 | 'shift' : { |
|
22 | 44 | help : '', |
|
23 | 45 | help_index : '', |
@@ -30,7 +52,7 b' var IPython = (function (IPython) {' | |||
|
30 | 52 | help : 'run cell, select below', |
|
31 | 53 | help_index : 'ba', |
|
32 | 54 | handler : function (event) { |
|
33 |
|
|
|
55 | that.notebook.execute_cell_and_select_below(); | |
|
34 | 56 | return false; |
|
35 | 57 | } |
|
36 | 58 | }, |
@@ -38,7 +60,7 b' var IPython = (function (IPython) {' | |||
|
38 | 60 | help : 'run cell', |
|
39 | 61 | help_index : 'bb', |
|
40 | 62 | handler : function (event) { |
|
41 |
|
|
|
63 | that.notebook.execute_cell(); | |
|
42 | 64 | return false; |
|
43 | 65 | } |
|
44 | 66 | }, |
@@ -46,44 +68,46 b' var IPython = (function (IPython) {' | |||
|
46 | 68 | help : 'run cell, insert below', |
|
47 | 69 | help_index : 'bc', |
|
48 | 70 | handler : function (event) { |
|
49 |
|
|
|
71 | that.notebook.execute_cell_and_insert_below(); | |
|
50 | 72 | return false; |
|
51 | 73 | } |
|
52 | 74 | } |
|
53 | 75 | }; |
|
54 | 76 | |
|
55 | 77 | if (platform === 'MacOS') { |
|
56 |
|
|
|
78 | shortcuts['cmd-s'] = | |
|
57 | 79 | { |
|
58 | 80 | help : 'save notebook', |
|
59 | 81 | help_index : 'fb', |
|
60 | 82 | handler : function (event) { |
|
61 |
|
|
|
83 | that.notebook.save_checkpoint(); | |
|
62 | 84 | event.preventDefault(); |
|
63 | 85 | return false; |
|
64 | 86 | } |
|
65 | 87 | }; |
|
66 | 88 | } else { |
|
67 |
|
|
|
89 | shortcuts['ctrl-s'] = | |
|
68 | 90 | { |
|
69 | 91 | help : 'save notebook', |
|
70 | 92 | help_index : 'fb', |
|
71 | 93 | handler : function (event) { |
|
72 |
|
|
|
94 | that.notebook.save_checkpoint(); | |
|
73 | 95 | event.preventDefault(); |
|
74 | 96 | return false; |
|
75 | 97 | } |
|
76 | 98 | }; |
|
77 | 99 | } |
|
100 | return shortcuts; | |
|
101 | }; | |
|
78 | 102 | |
|
79 | // Edit mode defaults | |
|
80 | ||
|
81 | var default_edit_shortcuts = { | |
|
103 | KeyboardManager.prototype.get_default_edit_shortcuts = function() { | |
|
104 | var that = this; | |
|
105 | return { | |
|
82 | 106 | 'esc' : { |
|
83 | 107 | help : 'command mode', |
|
84 | 108 | help_index : 'aa', |
|
85 | 109 | handler : function (event) { |
|
86 |
|
|
|
110 | that.notebook.command_mode(); | |
|
87 | 111 | return false; |
|
88 | 112 | } |
|
89 | 113 | }, |
@@ -91,7 +115,7 b' var IPython = (function (IPython) {' | |||
|
91 | 115 | help : 'command mode', |
|
92 | 116 | help_index : 'ab', |
|
93 | 117 | handler : function (event) { |
|
94 |
|
|
|
118 | that.notebook.command_mode(); | |
|
95 | 119 | return false; |
|
96 | 120 | } |
|
97 | 121 | }, |
@@ -99,14 +123,14 b' var IPython = (function (IPython) {' | |||
|
99 | 123 | help : '', |
|
100 | 124 | help_index : '', |
|
101 | 125 | handler : function (event) { |
|
102 |
var index = |
|
|
103 |
var cell = |
|
|
126 | var index = that.notebook.get_selected_index(); | |
|
127 | var cell = that.notebook.get_cell(index); | |
|
104 | 128 | if (cell && cell.at_top() && index !== 0) { |
|
105 | 129 | event.preventDefault(); |
|
106 |
|
|
|
107 |
|
|
|
108 |
|
|
|
109 |
var cm = |
|
|
130 | that.notebook.command_mode(); | |
|
131 | that.notebook.select_prev(); | |
|
132 | that.notebook.edit_mode(); | |
|
133 | var cm = that.notebook.get_selected_cell().code_mirror; | |
|
110 | 134 | cm.setCursor(cm.lastLine(), 0); |
|
111 | 135 | return false; |
|
112 | 136 | } else if (cell) { |
@@ -120,14 +144,14 b' var IPython = (function (IPython) {' | |||
|
120 | 144 | help : '', |
|
121 | 145 | help_index : '', |
|
122 | 146 | handler : function (event) { |
|
123 |
var index = |
|
|
124 |
var cell = |
|
|
125 |
if (cell.at_bottom() && index !== ( |
|
|
147 | var index = that.notebook.get_selected_index(); | |
|
148 | var cell = that.notebook.get_cell(index); | |
|
149 | if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) { | |
|
126 | 150 | event.preventDefault(); |
|
127 |
|
|
|
128 |
|
|
|
129 |
|
|
|
130 |
var cm = |
|
|
151 | that.notebook.command_mode(); | |
|
152 | that.notebook.select_next(); | |
|
153 | that.notebook.edit_mode(); | |
|
154 | var cm = that.notebook.get_selected_cell().code_mirror; | |
|
131 | 155 | cm.setCursor(0, 0); |
|
132 | 156 | return false; |
|
133 | 157 | } else { |
@@ -141,7 +165,7 b' var IPython = (function (IPython) {' | |||
|
141 | 165 | help : 'split cell', |
|
142 | 166 | help_index : 'ea', |
|
143 | 167 | handler : function (event) { |
|
144 |
|
|
|
168 | that.notebook.split_cell(); | |
|
145 | 169 | return false; |
|
146 | 170 | } |
|
147 | 171 | }, |
@@ -149,20 +173,21 b' var IPython = (function (IPython) {' | |||
|
149 | 173 | help : '', |
|
150 | 174 | help_index : 'eb', |
|
151 | 175 | handler : function (event) { |
|
152 |
|
|
|
176 | that.notebook.split_cell(); | |
|
153 | 177 | return false; |
|
154 | 178 | } |
|
155 | 179 | }, |
|
156 | 180 | }; |
|
181 | }; | |
|
157 | 182 | |
|
158 | // Command mode defaults | |
|
159 | ||
|
160 | var default_command_shortcuts = { | |
|
183 | KeyboardManager.prototype.get_default_command_shortcuts = function() { | |
|
184 | var that = this; | |
|
185 | return { | |
|
161 | 186 | 'enter' : { |
|
162 | 187 | help : 'edit mode', |
|
163 | 188 | help_index : 'aa', |
|
164 | 189 | handler : function (event) { |
|
165 |
|
|
|
190 | that.notebook.edit_mode(); | |
|
166 | 191 | return false; |
|
167 | 192 | } |
|
168 | 193 | }, |
@@ -170,10 +195,10 b' var IPython = (function (IPython) {' | |||
|
170 | 195 | help : 'select previous cell', |
|
171 | 196 | help_index : 'da', |
|
172 | 197 | handler : function (event) { |
|
173 |
var index = |
|
|
198 | var index = that.notebook.get_selected_index(); | |
|
174 | 199 | if (index !== 0 && index !== null) { |
|
175 |
|
|
|
176 |
|
|
|
200 | that.notebook.select_prev(); | |
|
201 | that.notebook.focus_cell(); | |
|
177 | 202 | } |
|
178 | 203 | return false; |
|
179 | 204 | } |
@@ -182,10 +207,10 b' var IPython = (function (IPython) {' | |||
|
182 | 207 | help : 'select next cell', |
|
183 | 208 | help_index : 'db', |
|
184 | 209 | handler : function (event) { |
|
185 |
var index = |
|
|
186 |
if (index !== ( |
|
|
187 |
|
|
|
188 |
|
|
|
210 | var index = that.notebook.get_selected_index(); | |
|
211 | if (index !== (that.notebook.ncells()-1) && index !== null) { | |
|
212 | that.notebook.select_next(); | |
|
213 | that.notebook.focus_cell(); | |
|
189 | 214 | } |
|
190 | 215 | return false; |
|
191 | 216 | } |
@@ -194,10 +219,10 b' var IPython = (function (IPython) {' | |||
|
194 | 219 | help : 'select previous cell', |
|
195 | 220 | help_index : 'dc', |
|
196 | 221 | handler : function (event) { |
|
197 |
var index = |
|
|
222 | var index = that.notebook.get_selected_index(); | |
|
198 | 223 | if (index !== 0 && index !== null) { |
|
199 |
|
|
|
200 |
|
|
|
224 | that.notebook.select_prev(); | |
|
225 | that.notebook.focus_cell(); | |
|
201 | 226 | } |
|
202 | 227 | return false; |
|
203 | 228 | } |
@@ -206,10 +231,10 b' var IPython = (function (IPython) {' | |||
|
206 | 231 | help : 'select next cell', |
|
207 | 232 | help_index : 'dd', |
|
208 | 233 | handler : function (event) { |
|
209 |
var index = |
|
|
210 |
if (index !== ( |
|
|
211 |
|
|
|
212 |
|
|
|
234 | var index = that.notebook.get_selected_index(); | |
|
235 | if (index !== (that.notebook.ncells()-1) && index !== null) { | |
|
236 | that.notebook.select_next(); | |
|
237 | that.notebook.focus_cell(); | |
|
213 | 238 | } |
|
214 | 239 | return false; |
|
215 | 240 | } |
@@ -218,7 +243,7 b' var IPython = (function (IPython) {' | |||
|
218 | 243 | help : 'cut cell', |
|
219 | 244 | help_index : 'ee', |
|
220 | 245 | handler : function (event) { |
|
221 |
|
|
|
246 | that.notebook.cut_cell(); | |
|
222 | 247 | return false; |
|
223 | 248 | } |
|
224 | 249 | }, |
@@ -226,7 +251,7 b' var IPython = (function (IPython) {' | |||
|
226 | 251 | help : 'copy cell', |
|
227 | 252 | help_index : 'ef', |
|
228 | 253 | handler : function (event) { |
|
229 |
|
|
|
254 | that.notebook.copy_cell(); | |
|
230 | 255 | return false; |
|
231 | 256 | } |
|
232 | 257 | }, |
@@ -234,7 +259,7 b' var IPython = (function (IPython) {' | |||
|
234 | 259 | help : 'paste cell above', |
|
235 | 260 | help_index : 'eg', |
|
236 | 261 | handler : function (event) { |
|
237 |
|
|
|
262 | that.notebook.paste_cell_above(); | |
|
238 | 263 | return false; |
|
239 | 264 | } |
|
240 | 265 | }, |
@@ -242,7 +267,7 b' var IPython = (function (IPython) {' | |||
|
242 | 267 | help : 'paste cell below', |
|
243 | 268 | help_index : 'eh', |
|
244 | 269 | handler : function (event) { |
|
245 |
|
|
|
270 | that.notebook.paste_cell_below(); | |
|
246 | 271 | return false; |
|
247 | 272 | } |
|
248 | 273 | }, |
@@ -251,7 +276,7 b' var IPython = (function (IPython) {' | |||
|
251 | 276 | help_index : 'ej', |
|
252 | 277 | count: 2, |
|
253 | 278 | handler : function (event) { |
|
254 |
|
|
|
279 | that.notebook.delete_cell(); | |
|
255 | 280 | return false; |
|
256 | 281 | } |
|
257 | 282 | }, |
@@ -259,9 +284,9 b' var IPython = (function (IPython) {' | |||
|
259 | 284 | help : 'insert cell above', |
|
260 | 285 | help_index : 'ec', |
|
261 | 286 | handler : function (event) { |
|
262 |
|
|
|
263 |
|
|
|
264 |
|
|
|
287 | that.notebook.insert_cell_above(); | |
|
288 | that.notebook.select_prev(); | |
|
289 | that.notebook.focus_cell(); | |
|
265 | 290 | return false; |
|
266 | 291 | } |
|
267 | 292 | }, |
@@ -269,9 +294,9 b' var IPython = (function (IPython) {' | |||
|
269 | 294 | help : 'insert cell below', |
|
270 | 295 | help_index : 'ed', |
|
271 | 296 | handler : function (event) { |
|
272 |
|
|
|
273 |
|
|
|
274 |
|
|
|
297 | that.notebook.insert_cell_below(); | |
|
298 | that.notebook.select_next(); | |
|
299 | that.notebook.focus_cell(); | |
|
275 | 300 | return false; |
|
276 | 301 | } |
|
277 | 302 | }, |
@@ -279,7 +304,7 b' var IPython = (function (IPython) {' | |||
|
279 | 304 | help : 'to code', |
|
280 | 305 | help_index : 'ca', |
|
281 | 306 | handler : function (event) { |
|
282 |
|
|
|
307 | that.notebook.to_code(); | |
|
283 | 308 | return false; |
|
284 | 309 | } |
|
285 | 310 | }, |
@@ -287,7 +312,7 b' var IPython = (function (IPython) {' | |||
|
287 | 312 | help : 'to markdown', |
|
288 | 313 | help_index : 'cb', |
|
289 | 314 | handler : function (event) { |
|
290 |
|
|
|
315 | that.notebook.to_markdown(); | |
|
291 | 316 | return false; |
|
292 | 317 | } |
|
293 | 318 | }, |
@@ -295,7 +320,7 b' var IPython = (function (IPython) {' | |||
|
295 | 320 | help : 'to raw', |
|
296 | 321 | help_index : 'cc', |
|
297 | 322 | handler : function (event) { |
|
298 |
|
|
|
323 | that.notebook.to_raw(); | |
|
299 | 324 | return false; |
|
300 | 325 | } |
|
301 | 326 | }, |
@@ -303,7 +328,7 b' var IPython = (function (IPython) {' | |||
|
303 | 328 | help : 'to heading 1', |
|
304 | 329 | help_index : 'cd', |
|
305 | 330 | handler : function (event) { |
|
306 |
|
|
|
331 | that.notebook.to_heading(undefined, 1); | |
|
307 | 332 | return false; |
|
308 | 333 | } |
|
309 | 334 | }, |
@@ -311,7 +336,7 b' var IPython = (function (IPython) {' | |||
|
311 | 336 | help : 'to heading 2', |
|
312 | 337 | help_index : 'ce', |
|
313 | 338 | handler : function (event) { |
|
314 |
|
|
|
339 | that.notebook.to_heading(undefined, 2); | |
|
315 | 340 | return false; |
|
316 | 341 | } |
|
317 | 342 | }, |
@@ -319,7 +344,7 b' var IPython = (function (IPython) {' | |||
|
319 | 344 | help : 'to heading 3', |
|
320 | 345 | help_index : 'cf', |
|
321 | 346 | handler : function (event) { |
|
322 |
|
|
|
347 | that.notebook.to_heading(undefined, 3); | |
|
323 | 348 | return false; |
|
324 | 349 | } |
|
325 | 350 | }, |
@@ -327,7 +352,7 b' var IPython = (function (IPython) {' | |||
|
327 | 352 | help : 'to heading 4', |
|
328 | 353 | help_index : 'cg', |
|
329 | 354 | handler : function (event) { |
|
330 |
|
|
|
355 | that.notebook.to_heading(undefined, 4); | |
|
331 | 356 | return false; |
|
332 | 357 | } |
|
333 | 358 | }, |
@@ -335,7 +360,7 b' var IPython = (function (IPython) {' | |||
|
335 | 360 | help : 'to heading 5', |
|
336 | 361 | help_index : 'ch', |
|
337 | 362 | handler : function (event) { |
|
338 |
|
|
|
363 | that.notebook.to_heading(undefined, 5); | |
|
339 | 364 | return false; |
|
340 | 365 | } |
|
341 | 366 | }, |
@@ -343,7 +368,7 b' var IPython = (function (IPython) {' | |||
|
343 | 368 | help : 'to heading 6', |
|
344 | 369 | help_index : 'ci', |
|
345 | 370 | handler : function (event) { |
|
346 |
|
|
|
371 | that.notebook.to_heading(undefined, 6); | |
|
347 | 372 | return false; |
|
348 | 373 | } |
|
349 | 374 | }, |
@@ -351,7 +376,7 b' var IPython = (function (IPython) {' | |||
|
351 | 376 | help : 'toggle output', |
|
352 | 377 | help_index : 'gb', |
|
353 | 378 | handler : function (event) { |
|
354 |
|
|
|
379 | that.notebook.toggle_output(); | |
|
355 | 380 | return false; |
|
356 | 381 | } |
|
357 | 382 | }, |
@@ -359,7 +384,7 b' var IPython = (function (IPython) {' | |||
|
359 | 384 | help : 'toggle output scrolling', |
|
360 | 385 | help_index : 'gc', |
|
361 | 386 | handler : function (event) { |
|
362 |
|
|
|
387 | that.notebook.toggle_output_scroll(); | |
|
363 | 388 | return false; |
|
364 | 389 | } |
|
365 | 390 | }, |
@@ -367,7 +392,7 b' var IPython = (function (IPython) {' | |||
|
367 | 392 | help : 'save notebook', |
|
368 | 393 | help_index : 'fa', |
|
369 | 394 | handler : function (event) { |
|
370 |
|
|
|
395 | that.notebook.save_checkpoint(); | |
|
371 | 396 | return false; |
|
372 | 397 | } |
|
373 | 398 | }, |
@@ -375,7 +400,7 b' var IPython = (function (IPython) {' | |||
|
375 | 400 | help : 'move cell down', |
|
376 | 401 | help_index : 'eb', |
|
377 | 402 | handler : function (event) { |
|
378 |
|
|
|
403 | that.notebook.move_cell_down(); | |
|
379 | 404 | return false; |
|
380 | 405 | } |
|
381 | 406 | }, |
@@ -383,7 +408,7 b' var IPython = (function (IPython) {' | |||
|
383 | 408 | help : 'move cell up', |
|
384 | 409 | help_index : 'ea', |
|
385 | 410 | handler : function (event) { |
|
386 |
|
|
|
411 | that.notebook.move_cell_up(); | |
|
387 | 412 | return false; |
|
388 | 413 | } |
|
389 | 414 | }, |
@@ -391,7 +416,7 b' var IPython = (function (IPython) {' | |||
|
391 | 416 | help : 'toggle line numbers', |
|
392 | 417 | help_index : 'ga', |
|
393 | 418 | handler : function (event) { |
|
394 |
|
|
|
419 | that.notebook.cell_toggle_line_numbers(); | |
|
395 | 420 | return false; |
|
396 | 421 | } |
|
397 | 422 | }, |
@@ -400,7 +425,7 b' var IPython = (function (IPython) {' | |||
|
400 | 425 | help_index : 'ha', |
|
401 | 426 | count: 2, |
|
402 | 427 | handler : function (event) { |
|
403 |
|
|
|
428 | that.notebook.kernel.interrupt(); | |
|
404 | 429 | return false; |
|
405 | 430 | } |
|
406 | 431 | }, |
@@ -409,7 +434,7 b' var IPython = (function (IPython) {' | |||
|
409 | 434 | help_index : 'hb', |
|
410 | 435 | count: 2, |
|
411 | 436 | handler : function (event) { |
|
412 |
|
|
|
437 | that.notebook.restart_kernel(); | |
|
413 | 438 | return false; |
|
414 | 439 | } |
|
415 | 440 | }, |
@@ -417,7 +442,7 b' var IPython = (function (IPython) {' | |||
|
417 | 442 | help : 'keyboard shortcuts', |
|
418 | 443 | help_index : 'ge', |
|
419 | 444 | handler : function (event) { |
|
420 |
|
|
|
445 | that.quick_help.show_keyboard_shortcuts(); | |
|
421 | 446 | return false; |
|
422 | 447 | } |
|
423 | 448 | }, |
@@ -425,7 +450,7 b' var IPython = (function (IPython) {' | |||
|
425 | 450 | help : 'undo last delete', |
|
426 | 451 | help_index : 'ei', |
|
427 | 452 | handler : function (event) { |
|
428 |
|
|
|
453 | that.notebook.undelete_cell(); | |
|
429 | 454 | return false; |
|
430 | 455 | } |
|
431 | 456 | }, |
@@ -433,7 +458,7 b' var IPython = (function (IPython) {' | |||
|
433 | 458 | help : 'merge cell below', |
|
434 | 459 | help_index : 'ek', |
|
435 | 460 | handler : function (event) { |
|
436 |
|
|
|
461 | that.notebook.merge_cell_below(); | |
|
437 | 462 | return false; |
|
438 | 463 | } |
|
439 | 464 | }, |
@@ -441,28 +466,11 b' var IPython = (function (IPython) {' | |||
|
441 | 466 | help : 'close pager', |
|
442 | 467 | help_index : 'gd', |
|
443 | 468 | handler : function (event) { |
|
444 |
|
|
|
469 | that.pager.collapse(); | |
|
445 | 470 | return false; |
|
446 | 471 | } |
|
447 | 472 | }, |
|
448 | 473 | }; |
|
449 | ||
|
450 | ||
|
451 | // Main keyboard manager for the notebook | |
|
452 | ||
|
453 | var ShortcutManager = IPython.keyboard.ShortcutManager; | |
|
454 | var keycodes = IPython.keyboard.keycodes; | |
|
455 | ||
|
456 | var KeyboardManager = function () { | |
|
457 | this.mode = 'command'; | |
|
458 | this.enabled = true; | |
|
459 | this.bind_events(); | |
|
460 | this.command_shortcuts = new ShortcutManager(); | |
|
461 | this.command_shortcuts.add_shortcuts(default_common_shortcuts); | |
|
462 | this.command_shortcuts.add_shortcuts(default_command_shortcuts); | |
|
463 | this.edit_shortcuts = new ShortcutManager(); | |
|
464 | this.edit_shortcuts.add_shortcuts(default_common_shortcuts); | |
|
465 | this.edit_shortcuts.add_shortcuts(default_edit_shortcuts); | |
|
466 | 474 | }; |
|
467 | 475 | |
|
468 | 476 | KeyboardManager.prototype.bind_events = function () { |
@@ -473,7 +481,7 b' var IPython = (function (IPython) {' | |||
|
473 | 481 | }; |
|
474 | 482 | |
|
475 | 483 | KeyboardManager.prototype.handle_keydown = function (event) { |
|
476 |
var notebook = |
|
|
484 | var notebook = this.notebook; | |
|
477 | 485 | |
|
478 | 486 | if (event.which === keycodes.esc) { |
|
479 | 487 | // Intercept escape at highest level to avoid closing |
@@ -545,18 +553,14 b' var IPython = (function (IPython) {' | |||
|
545 | 553 | // is_focused must be used to check for the case where an element within |
|
546 | 554 | // the element being removed is focused. |
|
547 | 555 | e.on('remove', function () { |
|
548 |
if ( |
|
|
556 | if (utils.is_focused(e[0])) { | |
|
549 | 557 | that.enable(); |
|
550 | 558 | } |
|
551 | 559 | }); |
|
552 | 560 | }; |
|
553 | 561 | |
|
554 | ||
|
555 | IPython.default_common_shortcuts = default_common_shortcuts; | |
|
556 | IPython.default_edit_shortcuts = default_edit_shortcuts; | |
|
557 | IPython.default_command_shortcuts = default_command_shortcuts; | |
|
562 | // For backwards compatability. | |
|
558 | 563 | IPython.KeyboardManager = KeyboardManager; |
|
559 | 564 | |
|
560 | return IPython; | |
|
561 | ||
|
562 | }(IPython)); | |
|
565 | return {'KeyboardManager': KeyboardManager}; | |
|
566 | }); |
@@ -1,19 +1,15 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // Layout | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | var IPython = (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | ], function(IPython, $) { | |
|
13 | 8 | "use strict"; |
|
14 | 9 | |
|
15 | 10 | var LayoutManager = function () { |
|
16 | 11 | this.bind_events(); |
|
12 | this.pager = undefined; | |
|
17 | 13 | }; |
|
18 | 14 | |
|
19 | 15 | LayoutManager.prototype.bind_events = function () { |
@@ -43,19 +39,20 b' var IPython = (function (IPython) {' | |||
|
43 | 39 | var app_height = this.app_height(); // content height |
|
44 | 40 | |
|
45 | 41 | $('#ipython-main-app').height(app_height); // content+padding+border height |
|
46 | ||
|
47 |
var pager_height = |
|
|
42 | if (this.pager) { | |
|
43 | var pager_height = this.pager.percentage_height*app_height; | |
|
48 | 44 | var pager_splitter_height = $('div#pager_splitter').outerHeight(true); |
|
49 | 45 | $('div#pager').outerHeight(pager_height); |
|
50 |
if ( |
|
|
46 | if (this.pager.expanded) { | |
|
51 | 47 | $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height); |
|
52 | 48 | } else { |
|
53 | 49 | $('div#notebook').outerHeight(app_height-pager_splitter_height); |
|
54 | 50 | } |
|
51 | } | |
|
55 | 52 | }; |
|
56 | 53 | |
|
54 | // Backwards compatability. | |
|
57 | 55 | IPython.LayoutManager = LayoutManager; |
|
58 | 56 | |
|
59 | return IPython; | |
|
60 | ||
|
61 | }(IPython)); | |
|
57 | return {'LayoutManager': LayoutManager}; | |
|
58 | }); |
@@ -1,78 +1,96 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // On document ready | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | // for the time beeing, we have to pass marked as a parameter here, | |
|
13 | // as injecting require.js make marked not to put itself in the globals, | |
|
14 | // which make both this file fail at setting marked configuration, and textcell.js | |
|
15 | // which search marked into global. | |
|
16 | require(['components/marked/lib/marked', | |
|
17 | 'widgets/js/init', | |
|
18 | 'components/bootstrap-tour/build/js/bootstrap-tour.min'], | |
|
19 | ||
|
20 | function (marked) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | require([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'notebook/js/notebook', | |
|
8 | 'base/js/utils', | |
|
9 | 'base/js/page', | |
|
10 | 'notebook/js/layoutmanager', | |
|
11 | 'base/js/events', | |
|
12 | 'auth/js/loginwidget', | |
|
13 | 'notebook/js/maintoolbar', | |
|
14 | 'notebook/js/pager', | |
|
15 | 'notebook/js/quickhelp', | |
|
16 | 'notebook/js/menubar', | |
|
17 | 'notebook/js/notificationarea', | |
|
18 | 'notebook/js/savewidget', | |
|
19 | 'notebook/js/keyboardmanager', | |
|
20 | 'notebook/js/config', | |
|
21 | 'notebook/js/kernelselector', | |
|
22 | // only loaded, not used: | |
|
23 | 'custom/custom', | |
|
24 | ], function( | |
|
25 | IPython, | |
|
26 | $, | |
|
27 | notebook, | |
|
28 | utils, | |
|
29 | page, | |
|
30 | layoutmanager, | |
|
31 | events, | |
|
32 | loginwidget, | |
|
33 | maintoolbar, | |
|
34 | pager, | |
|
35 | quickhelp, | |
|
36 | menubar, | |
|
37 | notificationarea, | |
|
38 | savewidget, | |
|
39 | keyboardmanager, | |
|
40 | config, | |
|
41 | kernelselector | |
|
42 | ) { | |
|
21 | 43 | "use strict"; |
|
22 | 44 | |
|
23 | window.marked = marked; | |
|
24 | ||
|
25 | // monkey patch CM to be able to syntax highlight cell magics | |
|
26 | // bug reported upstream, | |
|
27 | // see https://github.com/marijnh/CodeMirror2/issues/670 | |
|
28 | if(CodeMirror.getMode(1,'text/plain').indent === undefined ){ | |
|
29 | console.log('patching CM for undefined indent'); | |
|
30 | CodeMirror.modes.null = function() { | |
|
31 | return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}}; | |
|
32 | }; | |
|
33 | } | |
|
34 | ||
|
35 | CodeMirror.patchedGetMode = function(config, mode){ | |
|
36 | var cmmode = CodeMirror.getMode(config, mode); | |
|
37 | if(cmmode.indent === null) { | |
|
38 | console.log('patch mode "' , mode, '" on the fly'); | |
|
39 | cmmode.indent = function(){return 0;}; | |
|
40 | } | |
|
41 | return cmmode; | |
|
42 | }; | |
|
43 | // end monkey patching CodeMirror | |
|
44 | ||
|
45 | IPython.mathjaxutils.init(); | |
|
46 | ||
|
47 | $('#ipython-main-app').addClass('border-box-sizing'); | |
|
48 | $('div#notebook_panel').addClass('border-box-sizing'); | |
|
49 | ||
|
50 | var opts = { | |
|
51 | base_url : IPython.utils.get_body_data("baseUrl"), | |
|
52 | notebook_path : IPython.utils.get_body_data("notebookPath"), | |
|
53 | notebook_name : IPython.utils.get_body_data('notebookName') | |
|
45 | var common_options = { | |
|
46 | base_url : utils.get_body_data("baseUrl"), | |
|
47 | ws_url : IPython.utils.get_body_data("wsUrl"), | |
|
48 | notebook_path : utils.get_body_data("notebookPath"), | |
|
49 | notebook_name : utils.get_body_data('notebookName') | |
|
54 | 50 | }; |
|
55 | 51 | |
|
56 | IPython.page = new IPython.Page(); | |
|
57 | IPython.layout_manager = new IPython.LayoutManager(); | |
|
58 | IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter'); | |
|
59 | IPython.quick_help = new IPython.QuickHelp(); | |
|
60 | try { | |
|
61 | IPython.tour = new IPython.NotebookTour(); | |
|
62 | } catch (e) { | |
|
63 | console.log("Failed to instantiate Notebook Tour", e); | |
|
64 | } | |
|
65 | IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts); | |
|
66 | IPython.notebook = new IPython.Notebook('div#notebook', opts); | |
|
67 |
|
|
|
68 | IPython.save_widget = new IPython.SaveWidget('span#save_widget'); | |
|
69 | IPython.menubar = new IPython.MenuBar('#menubar', opts); | |
|
70 | IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container'); | |
|
71 | IPython.tooltip = new IPython.Tooltip(); | |
|
72 | IPython.notification_area = new IPython.NotificationArea('#notification_area'); | |
|
73 | IPython.notification_area.init_notification_widgets(); | |
|
74 | ||
|
75 | IPython.layout_manager.do_resize(); | |
|
52 | var user_config = $.extend({}, config.default_config); | |
|
53 | var page = new page.Page(); | |
|
54 | var layout_manager = new layoutmanager.LayoutManager(); | |
|
55 | var pager = new pager.Pager('div#pager', 'div#pager_splitter', { | |
|
56 | layout_manager: layout_manager, | |
|
57 | events: events}); | |
|
58 | var keyboard_manager = new keyboardmanager.KeyboardManager({ | |
|
59 | pager: pager, | |
|
60 | events: events}); | |
|
61 | var save_widget = new savewidget.SaveWidget('span#save_widget', { | |
|
62 | events: events, | |
|
63 | keyboard_manager: keyboard_manager}); | |
|
64 | var notebook = new notebook.Notebook('div#notebook', $.extend({ | |
|
65 | events: events, | |
|
66 | keyboard_manager: keyboard_manager, | |
|
67 | save_widget: save_widget, | |
|
68 | config: user_config}, | |
|
69 | common_options)); | |
|
70 | var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options); | |
|
71 | var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', { | |
|
72 | notebook: notebook, | |
|
73 | events: events}); | |
|
74 | var quick_help = new quickhelp.QuickHelp({ | |
|
75 | keyboard_manager: keyboard_manager, | |
|
76 | events: events, | |
|
77 | notebook: notebook}); | |
|
78 | var menubar = new menubar.MenuBar('#menubar', $.extend({ | |
|
79 | notebook: notebook, | |
|
80 | layout_manager: layout_manager, | |
|
81 | events: events, | |
|
82 | save_widget: save_widget, | |
|
83 | quick_help: quick_help}, | |
|
84 | common_options)); | |
|
85 | var notification_area = new notificationarea.NotificationArea( | |
|
86 | '#notification_area', { | |
|
87 | events: events, | |
|
88 | save_widget: save_widget, | |
|
89 | notebook: notebook, | |
|
90 | keyboard_manager: keyboard_manager}); | |
|
91 | notification_area.init_notification_widgets(); | |
|
92 | var kernel_selector = new kernelselector.KernelSelector( | |
|
93 | '#kernel_selector_widget', notebook); | |
|
76 | 94 | |
|
77 | 95 | $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+ |
|
78 | 96 | '<span id="test2" style="font-weight: bold;">x</span>'+ |
@@ -85,43 +103,37 b' function (marked) {' | |||
|
85 | 103 | } |
|
86 | 104 | $('#fonttest').remove(); |
|
87 | 105 | |
|
88 |
|
|
|
106 | page.show(); | |
|
89 | 107 | |
|
90 |
|
|
|
108 | layout_manager.do_resize(); | |
|
91 | 109 | var first_load = function () { |
|
92 |
|
|
|
110 | layout_manager.do_resize(); | |
|
93 | 111 | var hash = document.location.hash; |
|
94 | 112 | if (hash) { |
|
95 | 113 | document.location.hash = ''; |
|
96 | 114 | document.location.hash = hash; |
|
97 | 115 | } |
|
98 |
|
|
|
116 | notebook.set_autosave_interval(notebook.minimum_autosave_interval); | |
|
99 | 117 | // only do this once |
|
100 |
|
|
|
118 | events.off('notebook_loaded.Notebook', first_load); | |
|
101 | 119 | }; |
|
120 | events.on('notebook_loaded.Notebook', first_load); | |
|
121 | ||
|
122 | IPython.page = page; | |
|
123 | IPython.layout_manager = layout_manager; | |
|
124 | IPython.notebook = notebook; | |
|
125 | IPython.pager = pager; | |
|
126 | IPython.quick_help = quick_help; | |
|
127 | IPython.login_widget = login_widget; | |
|
128 | IPython.menubar = menubar; | |
|
129 | IPython.toolbar = toolbar; | |
|
130 | IPython.notification_area = notification_area; | |
|
131 | IPython.keyboard_manager = keyboard_manager; | |
|
132 | IPython.save_widget = save_widget; | |
|
133 | IPython.config = user_config; | |
|
134 | IPython.tooltip = notebook.tooltip; | |
|
135 | ||
|
136 | events.trigger('app_initialized.NotebookApp'); | |
|
137 | notebook.load_notebook(common_options.notebook_name, common_options.notebook_path); | |
|
102 | 138 | |
|
103 | $([IPython.events]).on('notebook_loaded.Notebook', first_load); | |
|
104 | $([IPython.events]).trigger('app_initialized.NotebookApp'); | |
|
105 | IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path); | |
|
106 | ||
|
107 | if (marked) { | |
|
108 | marked.setOptions({ | |
|
109 | gfm : true, | |
|
110 | tables: true, | |
|
111 | langPrefix: "language-", | |
|
112 | highlight: function(code, lang) { | |
|
113 | if (!lang) { | |
|
114 | // no language, no highlight | |
|
115 | return code; | |
|
116 | } | |
|
117 | var highlighted; | |
|
118 | try { | |
|
119 | highlighted = hljs.highlight(lang, code, false); | |
|
120 | } catch(err) { | |
|
121 | highlighted = hljs.highlightAuto(code); | |
|
122 | } | |
|
123 | return highlighted.value; | |
|
124 | } | |
|
125 | }); | |
|
126 | } | |
|
127 | 139 | }); |
@@ -1,35 +1,43 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // ToolBar | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | var IPython = (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'notebook/js/toolbar', | |
|
8 | 'notebook/js/celltoolbar', | |
|
9 | ], function(IPython, $, toolbar, celltoolbar) { | |
|
13 | 10 | "use strict"; |
|
14 | 11 | |
|
15 | var MainToolBar = function (selector) { | |
|
16 | IPython.ToolBar.apply(this, arguments); | |
|
12 | var MainToolBar = function (selector, options) { | |
|
13 | // Constructor | |
|
14 | // | |
|
15 | // Parameters: | |
|
16 | // selector: string | |
|
17 | // options: dictionary | |
|
18 | // Dictionary of keyword arguments. | |
|
19 | // events: $(Events) instance | |
|
20 | // notebook: Notebook instance | |
|
21 | toolbar.ToolBar.apply(this, arguments); | |
|
22 | this.events = options.events; | |
|
23 | this.notebook = options.notebook; | |
|
17 | 24 | this.construct(); |
|
18 | 25 | this.add_celltype_list(); |
|
19 | 26 | this.add_celltoolbar_list(); |
|
20 | 27 | this.bind_events(); |
|
21 | 28 | }; |
|
22 | 29 | |
|
23 |
MainToolBar.prototype = new |
|
|
30 | MainToolBar.prototype = new toolbar.ToolBar(); | |
|
24 | 31 | |
|
25 | 32 | MainToolBar.prototype.construct = function () { |
|
33 | var that = this; | |
|
26 | 34 | this.add_buttons_group([ |
|
27 | 35 | { |
|
28 | 36 | id : 'save_b', |
|
29 | 37 | label : 'Save and Checkpoint', |
|
30 |
icon : ' |
|
|
38 | icon : 'fa-save', | |
|
31 | 39 | callback : function () { |
|
32 |
|
|
|
40 | that.notebook.save_checkpoint(); | |
|
33 | 41 | } |
|
34 | 42 | } |
|
35 | 43 | ]); |
@@ -38,11 +46,11 b' var IPython = (function (IPython) {' | |||
|
38 | 46 | { |
|
39 | 47 | id : 'insert_below_b', |
|
40 | 48 | label : 'Insert Cell Below', |
|
41 |
icon : ' |
|
|
49 | icon : 'fa-plus', | |
|
42 | 50 | callback : function () { |
|
43 |
|
|
|
44 |
|
|
|
45 |
|
|
|
51 | that.notebook.insert_cell_below('code'); | |
|
52 | that.notebook.select_next(); | |
|
53 | that.notebook.focus_cell(); | |
|
46 | 54 | } |
|
47 | 55 | } |
|
48 | 56 | ],'insert_above_below'); |
@@ -51,25 +59,25 b' var IPython = (function (IPython) {' | |||
|
51 | 59 | { |
|
52 | 60 | id : 'cut_b', |
|
53 | 61 | label : 'Cut Cell', |
|
54 |
icon : ' |
|
|
62 | icon : 'fa-cut', | |
|
55 | 63 | callback : function () { |
|
56 |
|
|
|
64 | that.notebook.cut_cell(); | |
|
57 | 65 | } |
|
58 | 66 | }, |
|
59 | 67 | { |
|
60 | 68 | id : 'copy_b', |
|
61 | 69 | label : 'Copy Cell', |
|
62 |
icon : ' |
|
|
70 | icon : 'fa-copy', | |
|
63 | 71 | callback : function () { |
|
64 |
|
|
|
72 | that.notebook.copy_cell(); | |
|
65 | 73 | } |
|
66 | 74 | }, |
|
67 | 75 | { |
|
68 | 76 | id : 'paste_b', |
|
69 | 77 | label : 'Paste Cell Below', |
|
70 |
icon : ' |
|
|
78 | icon : 'fa-paste', | |
|
71 | 79 | callback : function () { |
|
72 |
|
|
|
80 | that.notebook.paste_cell_below(); | |
|
73 | 81 | } |
|
74 | 82 | } |
|
75 | 83 | ],'cut_copy_paste'); |
@@ -78,17 +86,17 b' var IPython = (function (IPython) {' | |||
|
78 | 86 | { |
|
79 | 87 | id : 'move_up_b', |
|
80 | 88 | label : 'Move Cell Up', |
|
81 |
icon : ' |
|
|
89 | icon : 'fa-arrow-up', | |
|
82 | 90 | callback : function () { |
|
83 |
|
|
|
91 | that.notebook.move_cell_up(); | |
|
84 | 92 | } |
|
85 | 93 | }, |
|
86 | 94 | { |
|
87 | 95 | id : 'move_down_b', |
|
88 | 96 | label : 'Move Cell Down', |
|
89 |
icon : ' |
|
|
97 | icon : 'fa-arrow-down', | |
|
90 | 98 | callback : function () { |
|
91 |
|
|
|
99 | that.notebook.move_cell_down(); | |
|
92 | 100 | } |
|
93 | 101 | } |
|
94 | 102 | ],'move_up_down'); |
@@ -98,26 +106,26 b' var IPython = (function (IPython) {' | |||
|
98 | 106 | { |
|
99 | 107 | id : 'run_b', |
|
100 | 108 | label : 'Run Cell', |
|
101 |
icon : ' |
|
|
109 | icon : 'fa-play', | |
|
102 | 110 | callback : function () { |
|
103 | 111 | // emulate default shift-enter behavior |
|
104 |
|
|
|
112 | that.notebook.execute_cell_and_select_below(); | |
|
105 | 113 | } |
|
106 | 114 | }, |
|
107 | 115 | { |
|
108 | 116 | id : 'interrupt_b', |
|
109 | 117 | label : 'Interrupt', |
|
110 |
icon : ' |
|
|
118 | icon : 'fa-stop', | |
|
111 | 119 | callback : function () { |
|
112 |
|
|
|
120 | that.notebook.session.interrupt_kernel(); | |
|
113 | 121 | } |
|
114 | 122 | }, |
|
115 | 123 | { |
|
116 | 124 | id : 'repeat_b', |
|
117 | 125 | label : 'Restart Kernel', |
|
118 |
icon : ' |
|
|
126 | icon : 'fa-repeat', | |
|
119 | 127 | callback : function () { |
|
120 |
|
|
|
128 | that.notebook.restart_kernel(); | |
|
121 | 129 | } |
|
122 | 130 | } |
|
123 | 131 | ],'run_int'); |
@@ -150,30 +158,31 b' var IPython = (function (IPython) {' | |||
|
150 | 158 | .addClass('form-control select-xs') |
|
151 | 159 | .append($('<option/>').attr('value', '').text('None')); |
|
152 | 160 | this.element.append(label).append(select); |
|
161 | var that = this; | |
|
153 | 162 | select.change(function() { |
|
154 | var val = $(this).val() | |
|
155 | if (val =='') { | |
|
156 |
|
|
|
157 |
delete |
|
|
163 | var val = $(this).val(); | |
|
164 | if (val ==='') { | |
|
165 | celltoolbar.CellToolbar.global_hide(); | |
|
166 | delete that.notebook.metadata.celltoolbar; | |
|
158 | 167 | } else { |
|
159 |
|
|
|
160 |
|
|
|
161 |
|
|
|
168 | celltoolbar.CellToolbar.global_show(); | |
|
169 | celltoolbar.CellToolbar.activate_preset(val, that.events); | |
|
170 | that.notebook.metadata.celltoolbar = val; | |
|
162 | 171 | } |
|
163 | 172 | }); |
|
164 | 173 | // Setup the currently registered presets. |
|
165 |
var presets = |
|
|
174 | var presets = celltoolbar.CellToolbar.list_presets(); | |
|
166 | 175 | for (var i=0; i<presets.length; i++) { |
|
167 | 176 | var name = presets[i]; |
|
168 | 177 | select.append($('<option/>').attr('value', name).text(name)); |
|
169 | 178 | } |
|
170 | 179 | // Setup future preset registrations. |
|
171 |
|
|
|
180 | this.events.on('preset_added.CellToolbar', function (event, data) { | |
|
172 | 181 | var name = data.name; |
|
173 | 182 | select.append($('<option/>').attr('value', name).text(name)); |
|
174 | 183 | }); |
|
175 | 184 | // Update select value when a preset is activated. |
|
176 |
|
|
|
185 | this.events.on('preset_activated.CellToolbar', function (event, data) { | |
|
177 | 186 | if (select.val() !== data.name) |
|
178 | 187 | select.val(data.name); |
|
179 | 188 | }); |
@@ -186,26 +195,26 b' var IPython = (function (IPython) {' | |||
|
186 | 195 | this.element.find('#cell_type').change(function () { |
|
187 | 196 | var cell_type = $(this).val(); |
|
188 | 197 | if (cell_type === 'code') { |
|
189 |
|
|
|
198 | that.notebook.to_code(); | |
|
190 | 199 | } else if (cell_type === 'markdown') { |
|
191 |
|
|
|
200 | that.notebook.to_markdown(); | |
|
192 | 201 | } else if (cell_type === 'raw') { |
|
193 |
|
|
|
202 | that.notebook.to_raw(); | |
|
194 | 203 | } else if (cell_type === 'heading1') { |
|
195 |
|
|
|
204 | that.notebook.to_heading(undefined, 1); | |
|
196 | 205 | } else if (cell_type === 'heading2') { |
|
197 |
|
|
|
206 | that.notebook.to_heading(undefined, 2); | |
|
198 | 207 | } else if (cell_type === 'heading3') { |
|
199 |
|
|
|
208 | that.notebook.to_heading(undefined, 3); | |
|
200 | 209 | } else if (cell_type === 'heading4') { |
|
201 |
|
|
|
210 | that.notebook.to_heading(undefined, 4); | |
|
202 | 211 | } else if (cell_type === 'heading5') { |
|
203 |
|
|
|
212 | that.notebook.to_heading(undefined, 5); | |
|
204 | 213 | } else if (cell_type === 'heading6') { |
|
205 |
|
|
|
214 | that.notebook.to_heading(undefined, 6); | |
|
206 | 215 | } |
|
207 | 216 | }); |
|
208 |
|
|
|
217 | this.events.on('selected_cell_type_changed.Notebook', function (event, data) { | |
|
209 | 218 | if (data.cell_type === 'heading') { |
|
210 | 219 | that.element.find('#cell_type').val(data.cell_type+data.level); |
|
211 | 220 | } else { |
@@ -214,8 +223,8 b' var IPython = (function (IPython) {' | |||
|
214 | 223 | }); |
|
215 | 224 | }; |
|
216 | 225 | |
|
226 | // Backwards compatability. | |
|
217 | 227 | IPython.MainToolBar = MainToolBar; |
|
218 | 228 | |
|
219 | return IPython; | |
|
220 | ||
|
221 | }(IPython)); | |
|
229 | return {'MainToolBar': MainToolBar}; | |
|
230 | }); |
@@ -1,18 +1,12 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008-2012 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // MathJax utility functions | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | ||
|
13 | IPython.namespace('IPython.mathjaxutils'); | |
|
14 | ||
|
15 | IPython.mathjaxutils = (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | 'base/js/dialog', | |
|
9 | ], function(IPython, $, utils, dialog) { | |
|
16 | 10 | "use strict"; |
|
17 | 11 | |
|
18 | 12 | var init = function () { |
@@ -75,7 +69,7 b' IPython.mathjaxutils = (function (IPython) {' | |||
|
75 | 69 | "which will prevent this dialog from appearing." |
|
76 | 70 | ) |
|
77 | 71 | ); |
|
78 |
|
|
|
72 | dialog.modal({ | |
|
79 | 73 | title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'", |
|
80 | 74 | body : message, |
|
81 | 75 | buttons : { |
@@ -110,7 +104,7 b' IPython.mathjaxutils = (function (IPython) {' | |||
|
110 | 104 | .replace(/</g, "<") // use HTML entity for < |
|
111 | 105 | .replace(/>/g, ">") // use HTML entity for > |
|
112 | 106 | ; |
|
113 |
if ( |
|
|
107 | if (utils.browser === 'msie') { | |
|
114 | 108 | block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n"); |
|
115 | 109 | } |
|
116 | 110 | while (j > i) { |
@@ -159,7 +153,7 b' IPython.mathjaxutils = (function (IPython) {' | |||
|
159 | 153 | de_tilde = function (text) { return text; }; |
|
160 | 154 | } |
|
161 | 155 | |
|
162 |
var blocks = |
|
|
156 | var blocks = utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT); | |
|
163 | 157 | |
|
164 | 158 | for (var i = 1, m = blocks.length; i < m; i += 2) { |
|
165 | 159 | var block = blocks[i]; |
@@ -242,10 +236,13 b' IPython.mathjaxutils = (function (IPython) {' | |||
|
242 | 236 | return text; |
|
243 | 237 | }; |
|
244 | 238 | |
|
245 | return { | |
|
239 | var mathjaxutils = { | |
|
246 | 240 | init : init, |
|
247 | 241 | remove_math : remove_math, |
|
248 | 242 | replace_math : replace_math |
|
249 | 243 | }; |
|
250 | 244 | |
|
251 | }(IPython)); | |
|
245 | IPython.mathjaxutils = mathjaxutils; | |
|
246 | ||
|
247 | return mathjaxutils; | |
|
248 | }); |
@@ -1,40 +1,49 b'' | |||
|
1 | 1 | // Copyright (c) IPython Development Team. |
|
2 | 2 | // Distributed under the terms of the Modified BSD License. |
|
3 | 3 | |
|
4 | //============================================================================ | |
|
5 | // MenuBar | |
|
6 | //============================================================================ | |
|
7 | ||
|
8 | /** | |
|
9 | * @module IPython | |
|
10 | * @namespace IPython | |
|
11 | * @submodule MenuBar | |
|
12 | */ | |
|
13 | ||
|
14 | ||
|
15 | var IPython = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | 'notebook/js/tour', | |
|
9 | 'bootstrap', | |
|
10 | 'moment', | |
|
11 | ], function(IPython, $, utils, tour, bootstrap, moment) { | |
|
16 | 12 | "use strict"; |
|
17 | 13 | |
|
18 | var utils = IPython.utils; | |
|
19 | ||
|
20 | /** | |
|
21 | * A MenuBar Class to generate the menubar of IPython notebook | |
|
22 | * @Class MenuBar | |
|
23 | * | |
|
24 | * @constructor | |
|
25 | * | |
|
26 | * | |
|
27 | * @param selector {string} selector for the menubar element in DOM | |
|
28 | * @param {object} [options] | |
|
29 | * @param [options.base_url] {String} String to use for the | |
|
30 | * base project url. Default is to inspect | |
|
31 | * $('body').data('baseUrl'); | |
|
32 | * does not support change for now is set through this option | |
|
33 | */ | |
|
34 | 14 | var MenuBar = function (selector, options) { |
|
15 | // Constructor | |
|
16 | // | |
|
17 | // A MenuBar Class to generate the menubar of IPython notebook | |
|
18 | // | |
|
19 | // Parameters: | |
|
20 | // selector: string | |
|
21 | // options: dictionary | |
|
22 | // Dictionary of keyword arguments. | |
|
23 | // notebook: Notebook instance | |
|
24 | // layout_manager: LayoutManager instance | |
|
25 | // events: $(Events) instance | |
|
26 | // save_widget: SaveWidget instance | |
|
27 | // quick_help: QuickHelp instance | |
|
28 | // base_url : string | |
|
29 | // notebook_path : string | |
|
30 | // notebook_name : string | |
|
35 | 31 | options = options || {}; |
|
36 |
this.base_url = options.base_url || |
|
|
32 | this.base_url = options.base_url || utils.get_body_data("baseUrl"); | |
|
37 | 33 | this.selector = selector; |
|
34 | this.notebook = options.notebook; | |
|
35 | this.layout_manager = options.layout_manager; | |
|
36 | this.events = options.events; | |
|
37 | this.save_widget = options.save_widget; | |
|
38 | this.quick_help = options.quick_help; | |
|
39 | ||
|
40 | try { | |
|
41 | this.tour = new tour.Tour(this.notebook, this.events); | |
|
42 | } catch (e) { | |
|
43 | this.tour = undefined; | |
|
44 | console.log("Failed to instantiate Notebook Tour", e); | |
|
45 | } | |
|
46 | ||
|
38 | 47 | if (this.selector !== undefined) { |
|
39 | 48 | this.element = $(selector); |
|
40 | 49 | this.style(); |
@@ -42,23 +51,24 b' var IPython = (function (IPython) {' | |||
|
42 | 51 | } |
|
43 | 52 | }; |
|
44 | 53 | |
|
54 | // TODO: This has definitively nothing to do with style ... | |
|
45 | 55 | MenuBar.prototype.style = function () { |
|
46 | this.element.addClass('border-box-sizing'); | |
|
56 | var that = this; | |
|
47 | 57 | this.element.find("li").click(function (event, ui) { |
|
48 | 58 | // The selected cell loses focus when the menu is entered, so we |
|
49 | 59 | // re-select it upon selection. |
|
50 |
var i = |
|
|
51 |
|
|
|
60 | var i = that.notebook.get_selected_index(); | |
|
61 | that.notebook.select(i); | |
|
52 | 62 | } |
|
53 | 63 | ); |
|
54 | 64 | }; |
|
55 | 65 | |
|
56 | 66 | MenuBar.prototype._nbconvert = function (format, download) { |
|
57 | 67 | download = download || false; |
|
58 |
var notebook_path = |
|
|
59 |
var notebook_name = |
|
|
60 |
if ( |
|
|
61 |
|
|
|
68 | var notebook_path = this.notebook.notebook_path; | |
|
69 | var notebook_name = this.notebook.notebook_name; | |
|
70 | if (this.notebook.dirty) { | |
|
71 | this.notebook.save_notebook({async : false}); | |
|
62 | 72 | } |
|
63 | 73 | var url = utils.url_join_encode( |
|
64 | 74 | this.base_url, |
@@ -75,25 +85,25 b' var IPython = (function (IPython) {' | |||
|
75 | 85 | // File |
|
76 | 86 | var that = this; |
|
77 | 87 | this.element.find('#new_notebook').click(function () { |
|
78 |
|
|
|
88 | that.notebook.new_notebook(); | |
|
79 | 89 | }); |
|
80 | 90 | this.element.find('#open_notebook').click(function () { |
|
81 | 91 | window.open(utils.url_join_encode( |
|
82 |
|
|
|
92 | that.notebook.base_url, | |
|
83 | 93 | 'tree', |
|
84 |
|
|
|
94 | that.notebook.notebook_path | |
|
85 | 95 | )); |
|
86 | 96 | }); |
|
87 | 97 | this.element.find('#copy_notebook').click(function () { |
|
88 |
|
|
|
98 | that.notebook.copy_notebook(); | |
|
89 | 99 | return false; |
|
90 | 100 | }); |
|
91 | 101 | this.element.find('#download_ipynb').click(function () { |
|
92 |
var base_url = |
|
|
93 |
var notebook_path = |
|
|
94 |
var notebook_name = |
|
|
95 |
if ( |
|
|
96 |
|
|
|
102 | var base_url = that.notebook.base_url; | |
|
103 | var notebook_path = that.notebook.notebook_path; | |
|
104 | var notebook_name = that.notebook.notebook_name; | |
|
105 | if (that.notebook.dirty) { | |
|
106 | that.notebook.save_notebook({async : false}); | |
|
97 | 107 | } |
|
98 | 108 | |
|
99 | 109 | var url = utils.url_join_encode( |
@@ -126,17 +136,17 b' var IPython = (function (IPython) {' | |||
|
126 | 136 | }); |
|
127 | 137 | |
|
128 | 138 | this.element.find('#rename_notebook').click(function () { |
|
129 |
|
|
|
139 | that.save_widget.rename_notebook({notebook: that.notebook}); | |
|
130 | 140 | }); |
|
131 | 141 | this.element.find('#save_checkpoint').click(function () { |
|
132 |
|
|
|
142 | that.notebook.save_checkpoint(); | |
|
133 | 143 | }); |
|
134 | 144 | this.element.find('#restore_checkpoint').click(function () { |
|
135 | 145 | }); |
|
136 | 146 | this.element.find('#trust_notebook').click(function () { |
|
137 |
|
|
|
147 | that.notebook.trust_notebook(); | |
|
138 | 148 | }); |
|
139 |
|
|
|
149 | this.events.on('trust_changed.Notebook', function (event, trusted) { | |
|
140 | 150 | if (trusted) { |
|
141 | 151 | that.element.find('#trust_notebook') |
|
142 | 152 | .addClass("disabled") |
@@ -148,157 +158,160 b' var IPython = (function (IPython) {' | |||
|
148 | 158 | } |
|
149 | 159 | }); |
|
150 | 160 | this.element.find('#kill_and_exit').click(function () { |
|
151 | IPython.notebook.session.delete(); | |
|
152 | setTimeout(function(){ | |
|
161 | var close_window = function () { | |
|
153 | 162 | // allow closing of new tabs in Chromium, impossible in FF |
|
154 | 163 | window.open('', '_self', ''); |
|
155 | 164 | window.close(); |
|
156 |
} |
|
|
165 | }; | |
|
166 | // finish with close on success or failure | |
|
167 | that.notebook.session.delete(close_window, close_window); | |
|
157 | 168 | }); |
|
158 | 169 | // Edit |
|
159 | 170 | this.element.find('#cut_cell').click(function () { |
|
160 |
|
|
|
171 | that.notebook.cut_cell(); | |
|
161 | 172 | }); |
|
162 | 173 | this.element.find('#copy_cell').click(function () { |
|
163 |
|
|
|
174 | that.notebook.copy_cell(); | |
|
164 | 175 | }); |
|
165 | 176 | this.element.find('#delete_cell').click(function () { |
|
166 |
|
|
|
177 | that.notebook.delete_cell(); | |
|
167 | 178 | }); |
|
168 | 179 | this.element.find('#undelete_cell').click(function () { |
|
169 |
|
|
|
180 | that.notebook.undelete_cell(); | |
|
170 | 181 | }); |
|
171 | 182 | this.element.find('#split_cell').click(function () { |
|
172 |
|
|
|
183 | that.notebook.split_cell(); | |
|
173 | 184 | }); |
|
174 | 185 | this.element.find('#merge_cell_above').click(function () { |
|
175 |
|
|
|
186 | that.notebook.merge_cell_above(); | |
|
176 | 187 | }); |
|
177 | 188 | this.element.find('#merge_cell_below').click(function () { |
|
178 |
|
|
|
189 | that.notebook.merge_cell_below(); | |
|
179 | 190 | }); |
|
180 | 191 | this.element.find('#move_cell_up').click(function () { |
|
181 |
|
|
|
192 | that.notebook.move_cell_up(); | |
|
182 | 193 | }); |
|
183 | 194 | this.element.find('#move_cell_down').click(function () { |
|
184 |
|
|
|
195 | that.notebook.move_cell_down(); | |
|
185 | 196 | }); |
|
186 | 197 | this.element.find('#edit_nb_metadata').click(function () { |
|
187 |
|
|
|
198 | that.notebook.edit_metadata({ | |
|
199 | notebook: that.notebook, | |
|
200 | keyboard_manager: that.notebook.keyboard_manager}); | |
|
188 | 201 | }); |
|
189 | 202 | |
|
190 | 203 | // View |
|
191 | 204 | this.element.find('#toggle_header').click(function () { |
|
192 | 205 | $('div#header').toggle(); |
|
193 |
|
|
|
206 | that.layout_manager.do_resize(); | |
|
194 | 207 | }); |
|
195 | 208 | this.element.find('#toggle_toolbar').click(function () { |
|
196 | 209 | $('div#maintoolbar').toggle(); |
|
197 |
|
|
|
210 | that.layout_manager.do_resize(); | |
|
198 | 211 | }); |
|
199 | 212 | // Insert |
|
200 | 213 | this.element.find('#insert_cell_above').click(function () { |
|
201 |
|
|
|
202 |
|
|
|
214 | that.notebook.insert_cell_above('code'); | |
|
215 | that.notebook.select_prev(); | |
|
203 | 216 | }); |
|
204 | 217 | this.element.find('#insert_cell_below').click(function () { |
|
205 |
|
|
|
206 |
|
|
|
218 | that.notebook.insert_cell_below('code'); | |
|
219 | that.notebook.select_next(); | |
|
207 | 220 | }); |
|
208 | 221 | // Cell |
|
209 | 222 | this.element.find('#run_cell').click(function () { |
|
210 |
|
|
|
223 | that.notebook.execute_cell(); | |
|
211 | 224 | }); |
|
212 | 225 | this.element.find('#run_cell_select_below').click(function () { |
|
213 |
|
|
|
226 | that.notebook.execute_cell_and_select_below(); | |
|
214 | 227 | }); |
|
215 | 228 | this.element.find('#run_cell_insert_below').click(function () { |
|
216 |
|
|
|
229 | that.notebook.execute_cell_and_insert_below(); | |
|
217 | 230 | }); |
|
218 | 231 | this.element.find('#run_all_cells').click(function () { |
|
219 |
|
|
|
232 | that.notebook.execute_all_cells(); | |
|
220 | 233 | }); |
|
221 | 234 | this.element.find('#run_all_cells_above').click(function () { |
|
222 |
|
|
|
235 | that.notebook.execute_cells_above(); | |
|
223 | 236 | }); |
|
224 | 237 | this.element.find('#run_all_cells_below').click(function () { |
|
225 |
|
|
|
238 | that.notebook.execute_cells_below(); | |
|
226 | 239 | }); |
|
227 | 240 | this.element.find('#to_code').click(function () { |
|
228 |
|
|
|
241 | that.notebook.to_code(); | |
|
229 | 242 | }); |
|
230 | 243 | this.element.find('#to_markdown').click(function () { |
|
231 |
|
|
|
244 | that.notebook.to_markdown(); | |
|
232 | 245 | }); |
|
233 | 246 | this.element.find('#to_raw').click(function () { |
|
234 |
|
|
|
247 | that.notebook.to_raw(); | |
|
235 | 248 | }); |
|
236 | 249 | this.element.find('#to_heading1').click(function () { |
|
237 |
|
|
|
250 | that.notebook.to_heading(undefined, 1); | |
|
238 | 251 | }); |
|
239 | 252 | this.element.find('#to_heading2').click(function () { |
|
240 |
|
|
|
253 | that.notebook.to_heading(undefined, 2); | |
|
241 | 254 | }); |
|
242 | 255 | this.element.find('#to_heading3').click(function () { |
|
243 |
|
|
|
256 | that.notebook.to_heading(undefined, 3); | |
|
244 | 257 | }); |
|
245 | 258 | this.element.find('#to_heading4').click(function () { |
|
246 |
|
|
|
259 | that.notebook.to_heading(undefined, 4); | |
|
247 | 260 | }); |
|
248 | 261 | this.element.find('#to_heading5').click(function () { |
|
249 |
|
|
|
262 | that.notebook.to_heading(undefined, 5); | |
|
250 | 263 | }); |
|
251 | 264 | this.element.find('#to_heading6').click(function () { |
|
252 |
|
|
|
265 | that.notebook.to_heading(undefined, 6); | |
|
253 | 266 | }); |
|
254 | 267 | |
|
255 | 268 | this.element.find('#toggle_current_output').click(function () { |
|
256 |
|
|
|
269 | that.notebook.toggle_output(); | |
|
257 | 270 | }); |
|
258 | 271 | this.element.find('#toggle_current_output_scroll').click(function () { |
|
259 |
|
|
|
272 | that.notebook.toggle_output_scroll(); | |
|
260 | 273 | }); |
|
261 | 274 | this.element.find('#clear_current_output').click(function () { |
|
262 |
|
|
|
275 | that.notebook.clear_output(); | |
|
263 | 276 | }); |
|
264 | 277 | |
|
265 | 278 | this.element.find('#toggle_all_output').click(function () { |
|
266 |
|
|
|
279 | that.notebook.toggle_all_output(); | |
|
267 | 280 | }); |
|
268 | 281 | this.element.find('#toggle_all_output_scroll').click(function () { |
|
269 |
|
|
|
282 | that.notebook.toggle_all_output_scroll(); | |
|
270 | 283 | }); |
|
271 | 284 | this.element.find('#clear_all_output').click(function () { |
|
272 |
|
|
|
285 | that.notebook.clear_all_output(); | |
|
273 | 286 | }); |
|
274 | 287 | |
|
275 | 288 | // Kernel |
|
276 | 289 | this.element.find('#int_kernel').click(function () { |
|
277 |
|
|
|
290 | that.notebook.session.interrupt_kernel(); | |
|
278 | 291 | }); |
|
279 | 292 | this.element.find('#restart_kernel').click(function () { |
|
280 |
|
|
|
293 | that.notebook.restart_kernel(); | |
|
281 | 294 | }); |
|
282 | 295 | // Help |
|
283 |
if ( |
|
|
296 | if (this.tour) { | |
|
284 | 297 | this.element.find('#notebook_tour').click(function () { |
|
285 |
|
|
|
298 | that.tour.start(); | |
|
286 | 299 | }); |
|
287 | 300 | } else { |
|
288 | 301 | this.element.find('#notebook_tour').addClass("disabled"); |
|
289 | 302 | } |
|
290 | 303 | this.element.find('#keyboard_shortcuts').click(function () { |
|
291 |
|
|
|
304 | that.quick_help.show_keyboard_shortcuts(); | |
|
292 | 305 | }); |
|
293 | 306 | |
|
294 | 307 | this.update_restore_checkpoint(null); |
|
295 | 308 | |
|
296 |
|
|
|
297 |
that.update_restore_checkpoint( |
|
|
309 | this.events.on('checkpoints_listed.Notebook', function (event, data) { | |
|
310 | that.update_restore_checkpoint(that.notebook.checkpoints); | |
|
298 | 311 | }); |
|
299 | 312 | |
|
300 |
|
|
|
301 |
that.update_restore_checkpoint( |
|
|
313 | this.events.on('checkpoint_created.Notebook', function (event, data) { | |
|
314 | that.update_restore_checkpoint(that.notebook.checkpoints); | |
|
302 | 315 | }); |
|
303 | 316 | }; |
|
304 | 317 | |
@@ -317,23 +330,24 b' var IPython = (function (IPython) {' | |||
|
317 | 330 | return; |
|
318 | 331 | } |
|
319 | 332 | |
|
333 | var that = this; | |
|
320 | 334 | checkpoints.map(function (checkpoint) { |
|
321 | 335 | var d = new Date(checkpoint.last_modified); |
|
322 | 336 | ul.append( |
|
323 | 337 | $("<li/>").append( |
|
324 | 338 | $("<a/>") |
|
325 | 339 | .attr("href", "#") |
|
326 |
.text(d.format(" |
|
|
340 | .text(moment(d).format("LLLL")) | |
|
327 | 341 | .click(function () { |
|
328 |
|
|
|
342 | that.notebook.restore_checkpoint_dialog(checkpoint); | |
|
329 | 343 | }) |
|
330 | 344 | ) |
|
331 | 345 | ); |
|
332 | 346 | }); |
|
333 | 347 | }; |
|
334 | 348 | |
|
349 | // Backwards compatability. | |
|
335 | 350 | IPython.MenuBar = MenuBar; |
|
336 | 351 | |
|
337 | return IPython; | |
|
338 | ||
|
339 | }(IPython)); | |
|
352 | return {'MenuBar': MenuBar}; | |
|
353 | }); |
This diff has been collapsed as it changes many lines, (510 lines changed) Show them Hide them | |||
@@ -1,32 +1,99 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // Notebook | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | var IPython = (function (IPython) { | |
|
13 | "use strict"; | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | 'base/js/dialog', | |
|
9 | 'notebook/js/textcell', | |
|
10 | 'notebook/js/codecell', | |
|
11 | 'services/sessions/js/session', | |
|
12 | 'notebook/js/celltoolbar', | |
|
13 | 'components/marked/lib/marked', | |
|
14 | 'highlight', | |
|
15 | 'notebook/js/mathjaxutils', | |
|
16 | 'base/js/keyboard', | |
|
17 | 'notebook/js/tooltip', | |
|
18 | 'notebook/js/celltoolbarpresets/default', | |
|
19 | 'notebook/js/celltoolbarpresets/rawcell', | |
|
20 | 'notebook/js/celltoolbarpresets/slideshow', | |
|
21 | ], function ( | |
|
22 | IPython, | |
|
23 | $, | |
|
24 | utils, | |
|
25 | dialog, | |
|
26 | textcell, | |
|
27 | codecell, | |
|
28 | session, | |
|
29 | celltoolbar, | |
|
30 | marked, | |
|
31 | hljs, | |
|
32 | mathjaxutils, | |
|
33 | keyboard, | |
|
34 | tooltip, | |
|
35 | default_celltoolbar, | |
|
36 | rawcell_celltoolbar, | |
|
37 | slideshow_celltoolbar | |
|
38 | ) { | |
|
14 | 39 | |
|
15 | var utils = IPython.utils; | |
|
16 | ||
|
17 | /** | |
|
18 | * A notebook contains and manages cells. | |
|
19 | * | |
|
20 | * @class Notebook | |
|
21 | * @constructor | |
|
22 | * @param {String} selector A jQuery selector for the notebook's DOM element | |
|
23 | * @param {Object} [options] A config object | |
|
24 | */ | |
|
25 | 40 | var Notebook = function (selector, options) { |
|
26 | this.options = options = options || {}; | |
|
41 | // Constructor | |
|
42 | // | |
|
43 | // A notebook contains and manages cells. | |
|
44 | // | |
|
45 | // Parameters: | |
|
46 | // selector: string | |
|
47 | // options: dictionary | |
|
48 | // Dictionary of keyword arguments. | |
|
49 | // events: $(Events) instance | |
|
50 | // keyboard_manager: KeyboardManager instance | |
|
51 | // save_widget: SaveWidget instance | |
|
52 | // config: dictionary | |
|
53 | // base_url : string | |
|
54 | // notebook_path : string | |
|
55 | // notebook_name : string | |
|
56 | this.config = options.config || {}; | |
|
27 | 57 | this.base_url = options.base_url; |
|
28 | 58 | this.notebook_path = options.notebook_path; |
|
29 | 59 | this.notebook_name = options.notebook_name; |
|
60 | this.events = options.events; | |
|
61 | this.keyboard_manager = options.keyboard_manager; | |
|
62 | this.save_widget = options.save_widget; | |
|
63 | this.tooltip = new tooltip.Tooltip(this.events); | |
|
64 | this.ws_url = options.ws_url; | |
|
65 | this._session_starting = false; | |
|
66 | // default_kernel_name is a temporary measure while we implement proper | |
|
67 | // kernel selection and delayed start. Do not rely on it. | |
|
68 | this.default_kernel_name = 'python'; | |
|
69 | // TODO: This code smells (and the other `= this` line a couple lines down) | |
|
70 | // We need a better way to deal with circular instance references. | |
|
71 | this.keyboard_manager.notebook = this; | |
|
72 | this.save_widget.notebook = this; | |
|
73 | ||
|
74 | mathjaxutils.init(); | |
|
75 | ||
|
76 | if (marked) { | |
|
77 | marked.setOptions({ | |
|
78 | gfm : true, | |
|
79 | tables: true, | |
|
80 | langPrefix: "language-", | |
|
81 | highlight: function(code, lang) { | |
|
82 | if (!lang) { | |
|
83 | // no language, no highlight | |
|
84 | return code; | |
|
85 | } | |
|
86 | var highlighted; | |
|
87 | try { | |
|
88 | highlighted = hljs.highlight(lang, code, false); | |
|
89 | } catch(err) { | |
|
90 | highlighted = hljs.highlightAuto(code); | |
|
91 | } | |
|
92 | return highlighted.value; | |
|
93 | } | |
|
94 | }); | |
|
95 | } | |
|
96 | ||
|
30 | 97 | this.element = $(selector); |
|
31 | 98 | this.element.scroll(); |
|
32 | 99 | this.element.data("notebook", this); |
@@ -55,23 +122,20 b' var IPython = (function (IPython) {' | |||
|
55 | 122 | this.notebook_name_blacklist_re = /[\/\\:]/; |
|
56 | 123 | this.nbformat = 3; // Increment this when changing the nbformat |
|
57 | 124 | this.nbformat_minor = 0; // Increment this when changing the nbformat |
|
58 | this.style(); | |
|
125 | this.codemirror_mode = 'ipython'; | |
|
59 | 126 | this.create_elements(); |
|
60 | 127 | this.bind_events(); |
|
61 | 128 | this.save_notebook = function() { // don't allow save until notebook_loaded |
|
62 | 129 | this.save_notebook_error(null, null, "Load failed, save is disabled"); |
|
63 | 130 | }; |
|
64 | }; | |
|
65 | 131 | |
|
66 | /** | |
|
67 | * Tweak the notebook's CSS style. | |
|
68 | * | |
|
69 | * @method style | |
|
70 | */ | |
|
71 | Notebook.prototype.style = function () { | |
|
72 | $('div#notebook').addClass('border-box-sizing'); | |
|
132 | // Trigger cell toolbar registration. | |
|
133 | default_celltoolbar.register(this); | |
|
134 | rawcell_celltoolbar.register(this); | |
|
135 | slideshow_celltoolbar.register(this); | |
|
73 | 136 | }; |
|
74 | 137 | |
|
138 | ||
|
75 | 139 | /** |
|
76 | 140 | * Create an HTML and CSS representation of the notebook. |
|
77 | 141 | * |
@@ -102,36 +166,38 b' var IPython = (function (IPython) {' | |||
|
102 | 166 | Notebook.prototype.bind_events = function () { |
|
103 | 167 | var that = this; |
|
104 | 168 | |
|
105 |
|
|
|
169 | this.events.on('set_next_input.Notebook', function (event, data) { | |
|
106 | 170 | var index = that.find_cell_index(data.cell); |
|
107 | 171 | var new_cell = that.insert_cell_below('code',index); |
|
108 | 172 | new_cell.set_text(data.text); |
|
109 | 173 | that.dirty = true; |
|
110 | 174 | }); |
|
111 | 175 | |
|
112 |
|
|
|
176 | this.events.on('set_dirty.Notebook', function (event, data) { | |
|
113 | 177 | that.dirty = data.value; |
|
114 | 178 | }); |
|
115 | 179 | |
|
116 |
|
|
|
180 | this.events.on('trust_changed.Notebook', function (event, data) { | |
|
117 | 181 | that.trusted = data.value; |
|
118 | 182 | }); |
|
119 | 183 | |
|
120 |
|
|
|
184 | this.events.on('select.Cell', function (event, data) { | |
|
121 | 185 | var index = that.find_cell_index(data.cell); |
|
122 | 186 | that.select(index); |
|
123 | 187 | }); |
|
124 | 188 | |
|
125 |
|
|
|
189 | this.events.on('edit_mode.Cell', function (event, data) { | |
|
126 | 190 | that.handle_edit_mode(data.cell); |
|
127 | 191 | }); |
|
128 | 192 | |
|
129 |
|
|
|
193 | this.events.on('command_mode.Cell', function (event, data) { | |
|
130 | 194 | that.handle_command_mode(data.cell); |
|
131 | 195 | }); |
|
132 | 196 | |
|
133 |
|
|
|
134 |
|
|
|
197 | this.events.on('status_autorestarting.Kernel', function () { | |
|
198 | dialog.modal({ | |
|
199 | notebook: that, | |
|
200 | keyboard_manager: that.keyboard_manager, | |
|
135 | 201 | title: "Kernel Restarting", |
|
136 | 202 | body: "The kernel appears to have died. It will restart automatically.", |
|
137 | 203 | buttons: { |
@@ -142,6 +208,13 b' var IPython = (function (IPython) {' | |||
|
142 | 208 | }); |
|
143 | 209 | }); |
|
144 | 210 | |
|
211 | this.events.on('spec_changed.Kernel', function(event, data) { | |
|
212 | that.set_kernelspec_metadata(data); | |
|
213 | if (data.codemirror_mode) { | |
|
214 | that.set_codemirror_mode(data.codemirror_mode); | |
|
215 | } | |
|
216 | }); | |
|
217 | ||
|
145 | 218 | var collapse_time = function (time) { |
|
146 | 219 | var app_height = $('#ipython-main-app').height(); // content height |
|
147 | 220 | var splitter_height = $('div#pager_splitter').outerHeight(true); |
@@ -211,7 +284,7 b' var IPython = (function (IPython) {' | |||
|
211 | 284 | if (this.dirty == value) { |
|
212 | 285 | return; |
|
213 | 286 | } |
|
214 |
|
|
|
287 | this.events.trigger('set_dirty.Notebook', {value: value}); | |
|
215 | 288 | }; |
|
216 | 289 | |
|
217 | 290 | /** |
@@ -254,11 +327,26 b' var IPython = (function (IPython) {' | |||
|
254 | 327 | |
|
255 | 328 | Notebook.prototype.edit_metadata = function () { |
|
256 | 329 | var that = this; |
|
257 |
|
|
|
330 | dialog.edit_metadata({ | |
|
331 | md: this.metadata, | |
|
332 | callback: function (md) { | |
|
258 | 333 | that.metadata = md; |
|
259 | }, 'Notebook'); | |
|
334 | }, | |
|
335 | name: 'Notebook', | |
|
336 | notebook: this, | |
|
337 | keyboard_manager: this.keyboard_manager}); | |
|
260 | 338 | }; |
|
261 | 339 | |
|
340 | Notebook.prototype.set_kernelspec_metadata = function(ks) { | |
|
341 | var tostore = {}; | |
|
342 | $.map(ks, function(value, field) { | |
|
343 | if (field !== 'argv' && field !== 'env') { | |
|
344 | tostore[field] = value; | |
|
345 | } | |
|
346 | }); | |
|
347 | this.metadata.kernelspec = tostore; | |
|
348 | } | |
|
349 | ||
|
262 | 350 | // Cell indexing, retrieval, etc. |
|
263 | 351 | |
|
264 | 352 | /** |
@@ -295,7 +383,7 b' var IPython = (function (IPython) {' | |||
|
295 | 383 | * @return {Cell} Cell or null if no cell was found. |
|
296 | 384 | */ |
|
297 | 385 | Notebook.prototype.get_msg_cell = function (msg_id) { |
|
298 |
return |
|
|
386 | return codecell.CodeCell.msg_cells[msg_id] || null; | |
|
299 | 387 | }; |
|
300 | 388 | |
|
301 | 389 | /** |
@@ -474,11 +562,11 b' var IPython = (function (IPython) {' | |||
|
474 | 562 | var cell = this.get_cell(index); |
|
475 | 563 | cell.select(); |
|
476 | 564 | if (cell.cell_type === 'heading') { |
|
477 |
|
|
|
565 | this.events.trigger('selected_cell_type_changed.Notebook', | |
|
478 | 566 | {'cell_type':cell.cell_type,level:cell.level} |
|
479 | 567 | ); |
|
480 | 568 | } else { |
|
481 |
|
|
|
569 | this.events.trigger('selected_cell_type_changed.Notebook', | |
|
482 | 570 | {'cell_type':cell.cell_type} |
|
483 | 571 | ); |
|
484 | 572 | } |
@@ -540,8 +628,8 b' var IPython = (function (IPython) {' | |||
|
540 | 628 | if (this.mode !== 'command') { |
|
541 | 629 | cell.command_mode(); |
|
542 | 630 | this.mode = 'command'; |
|
543 |
|
|
|
544 |
|
|
|
631 | this.events.trigger('command_mode.Notebook'); | |
|
632 | this.keyboard_manager.command_mode(); | |
|
545 | 633 | } |
|
546 | 634 | }; |
|
547 | 635 | |
@@ -570,8 +658,8 b' var IPython = (function (IPython) {' | |||
|
570 | 658 | if (cell && this.mode !== 'edit') { |
|
571 | 659 | cell.edit_mode(); |
|
572 | 660 | this.mode = 'edit'; |
|
573 |
|
|
|
574 |
|
|
|
661 | this.events.trigger('edit_mode.Notebook'); | |
|
662 | this.keyboard_manager.edit_mode(); | |
|
575 | 663 | } |
|
576 | 664 | }; |
|
577 | 665 | |
@@ -686,7 +774,7 b' var IPython = (function (IPython) {' | |||
|
686 | 774 | this.undelete_index = i; |
|
687 | 775 | this.undelete_below = false; |
|
688 | 776 | } |
|
689 |
|
|
|
777 | this.events.trigger('delete.Cell', {'cell': cell, 'index': i}); | |
|
690 | 778 | this.set_dirty(true); |
|
691 | 779 | } |
|
692 | 780 | return this; |
@@ -753,20 +841,27 b' var IPython = (function (IPython) {' | |||
|
753 | 841 | type = type || this.get_selected_cell().cell_type; |
|
754 | 842 | |
|
755 | 843 | if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) { |
|
844 | var cell_options = { | |
|
845 | events: this.events, | |
|
846 | config: this.config, | |
|
847 | keyboard_manager: this.keyboard_manager, | |
|
848 | notebook: this, | |
|
849 | tooltip: this.tooltip, | |
|
850 | }; | |
|
756 | 851 | if (type === 'code') { |
|
757 |
cell = new |
|
|
852 | cell = new codecell.CodeCell(this.kernel, cell_options); | |
|
758 | 853 | cell.set_input_prompt(); |
|
759 | 854 | } else if (type === 'markdown') { |
|
760 |
cell = new |
|
|
855 | cell = new textcell.MarkdownCell(cell_options); | |
|
761 | 856 | } else if (type === 'raw') { |
|
762 |
cell = new |
|
|
857 | cell = new textcell.RawCell(cell_options); | |
|
763 | 858 | } else if (type === 'heading') { |
|
764 |
cell = new |
|
|
859 | cell = new textcell.HeadingCell(cell_options); | |
|
765 | 860 | } |
|
766 | 861 | |
|
767 | 862 | if(this._insert_element_at_index(cell.element,index)) { |
|
768 | 863 | cell.render(); |
|
769 |
|
|
|
864 | this.events.trigger('create.Cell', {'cell': cell, 'index': index}); | |
|
770 | 865 | cell.refresh(); |
|
771 | 866 | // We used to select the cell after we refresh it, but there |
|
772 | 867 | // are now cases were this method is called where select is |
@@ -876,7 +971,7 b' var IPython = (function (IPython) {' | |||
|
876 | 971 | if (this.is_valid_cell_index(i)) { |
|
877 | 972 | var source_element = this.get_cell_element(i); |
|
878 | 973 | var source_cell = source_element.data("cell"); |
|
879 |
if (!(source_cell instanceof |
|
|
974 | if (!(source_cell instanceof codecell.CodeCell)) { | |
|
880 | 975 | var target_cell = this.insert_cell_below('code',i); |
|
881 | 976 | var text = source_cell.get_text(); |
|
882 | 977 | if (text === source_cell.placeholder) { |
@@ -906,7 +1001,7 b' var IPython = (function (IPython) {' | |||
|
906 | 1001 | if (this.is_valid_cell_index(i)) { |
|
907 | 1002 | var source_element = this.get_cell_element(i); |
|
908 | 1003 | var source_cell = source_element.data("cell"); |
|
909 |
if (!(source_cell instanceof |
|
|
1004 | if (!(source_cell instanceof textcell.MarkdownCell)) { | |
|
910 | 1005 | var target_cell = this.insert_cell_below('markdown',i); |
|
911 | 1006 | var text = source_cell.get_text(); |
|
912 | 1007 | if (text === source_cell.placeholder) { |
@@ -920,7 +1015,7 b' var IPython = (function (IPython) {' | |||
|
920 | 1015 | target_cell.code_mirror.clearHistory(); |
|
921 | 1016 | source_element.remove(); |
|
922 | 1017 | this.select(i); |
|
923 |
if ((source_cell instanceof |
|
|
1018 | if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) { | |
|
924 | 1019 | target_cell.render(); |
|
925 | 1020 | } |
|
926 | 1021 | var cursor = source_cell.code_mirror.getCursor(); |
@@ -942,7 +1037,7 b' var IPython = (function (IPython) {' | |||
|
942 | 1037 | var source_element = this.get_cell_element(i); |
|
943 | 1038 | var source_cell = source_element.data("cell"); |
|
944 | 1039 | var target_cell = null; |
|
945 |
if (!(source_cell instanceof |
|
|
1040 | if (!(source_cell instanceof textcell.RawCell)) { | |
|
946 | 1041 | target_cell = this.insert_cell_below('raw',i); |
|
947 | 1042 | var text = source_cell.get_text(); |
|
948 | 1043 | if (text === source_cell.placeholder) { |
@@ -977,7 +1072,7 b' var IPython = (function (IPython) {' | |||
|
977 | 1072 | var source_element = this.get_cell_element(i); |
|
978 | 1073 | var source_cell = source_element.data("cell"); |
|
979 | 1074 | var target_cell = null; |
|
980 |
if (source_cell instanceof |
|
|
1075 | if (source_cell instanceof textcell.HeadingCell) { | |
|
981 | 1076 | source_cell.set_level(level); |
|
982 | 1077 | } else { |
|
983 | 1078 | target_cell = this.insert_cell_below('heading',i); |
@@ -996,12 +1091,12 b' var IPython = (function (IPython) {' | |||
|
996 | 1091 | this.select(i); |
|
997 | 1092 | var cursor = source_cell.code_mirror.getCursor(); |
|
998 | 1093 | target_cell.code_mirror.setCursor(cursor); |
|
999 |
if ((source_cell instanceof |
|
|
1094 | if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) { | |
|
1000 | 1095 | target_cell.render(); |
|
1001 | 1096 | } |
|
1002 | 1097 | } |
|
1003 | 1098 | this.set_dirty(true); |
|
1004 |
|
|
|
1099 | this.events.trigger('selected_cell_type_changed.Notebook', | |
|
1005 | 1100 | {'cell_type':'heading',level:level} |
|
1006 | 1101 | ); |
|
1007 | 1102 | } |
@@ -1115,27 +1210,18 b' var IPython = (function (IPython) {' | |||
|
1115 | 1210 | * @method split_cell |
|
1116 | 1211 | */ |
|
1117 | 1212 | Notebook.prototype.split_cell = function () { |
|
1118 |
var mdc = |
|
|
1119 |
var rc = |
|
|
1213 | var mdc = textcell.MarkdownCell; | |
|
1214 | var rc = textcell.RawCell; | |
|
1120 | 1215 | var cell = this.get_selected_cell(); |
|
1121 | 1216 | if (cell.is_splittable()) { |
|
1122 | 1217 | var texta = cell.get_pre_cursor(); |
|
1123 | 1218 | var textb = cell.get_post_cursor(); |
|
1124 | if (cell instanceof IPython.CodeCell) { | |
|
1125 | // In this case the operations keep the notebook in its existing mode | |
|
1126 | // so we don't need to do any post-op mode changes. | |
|
1127 | cell.set_text(textb); | |
|
1128 | var new_cell = this.insert_cell_above('code'); | |
|
1129 | new_cell.set_text(texta); | |
|
1130 | } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) { | |
|
1131 | // We know cell is !rendered so we can use set_text. | |
|
1132 | 1219 |
|
|
1133 | 1220 |
|
|
1134 | 1221 |
|
|
1135 | 1222 |
|
|
1136 | 1223 |
|
|
1137 | 1224 |
|
|
1138 | } | |
|
1139 | 1225 | }; |
|
1140 | 1226 | |
|
1141 | 1227 | /** |
@@ -1144,8 +1230,8 b' var IPython = (function (IPython) {' | |||
|
1144 | 1230 | * @method merge_cell_above |
|
1145 | 1231 | */ |
|
1146 | 1232 | Notebook.prototype.merge_cell_above = function () { |
|
1147 |
var mdc = |
|
|
1148 |
var rc = |
|
|
1233 | var mdc = textcell.MarkdownCell; | |
|
1234 | var rc = textcell.RawCell; | |
|
1149 | 1235 | var index = this.get_selected_index(); |
|
1150 | 1236 | var cell = this.get_cell(index); |
|
1151 | 1237 | var render = cell.rendered; |
@@ -1159,9 +1245,9 b' var IPython = (function (IPython) {' | |||
|
1159 | 1245 | } |
|
1160 | 1246 | var upper_text = upper_cell.get_text(); |
|
1161 | 1247 | var text = cell.get_text(); |
|
1162 |
if (cell instanceof |
|
|
1248 | if (cell instanceof codecell.CodeCell) { | |
|
1163 | 1249 | cell.set_text(upper_text+'\n'+text); |
|
1164 | } else if ((cell instanceof mdc) || (cell instanceof rc)) { | |
|
1250 | } else { | |
|
1165 | 1251 | cell.unrender(); // Must unrender before we set_text. |
|
1166 | 1252 | cell.set_text(upper_text+'\n\n'+text); |
|
1167 | 1253 | if (render) { |
@@ -1181,8 +1267,8 b' var IPython = (function (IPython) {' | |||
|
1181 | 1267 | * @method merge_cell_below |
|
1182 | 1268 | */ |
|
1183 | 1269 | Notebook.prototype.merge_cell_below = function () { |
|
1184 |
var mdc = |
|
|
1185 |
var rc = |
|
|
1270 | var mdc = textcell.MarkdownCell; | |
|
1271 | var rc = textcell.RawCell; | |
|
1186 | 1272 | var index = this.get_selected_index(); |
|
1187 | 1273 | var cell = this.get_cell(index); |
|
1188 | 1274 | var render = cell.rendered; |
@@ -1196,9 +1282,9 b' var IPython = (function (IPython) {' | |||
|
1196 | 1282 | } |
|
1197 | 1283 | var lower_text = lower_cell.get_text(); |
|
1198 | 1284 | var text = cell.get_text(); |
|
1199 |
if (cell instanceof |
|
|
1285 | if (cell instanceof codecell.CodeCell) { | |
|
1200 | 1286 | cell.set_text(text+'\n'+lower_text); |
|
1201 | } else if ((cell instanceof mdc) || (cell instanceof rc)) { | |
|
1287 | } else { | |
|
1202 | 1288 | cell.unrender(); // Must unrender before we set_text. |
|
1203 | 1289 | cell.set_text(text+'\n\n'+lower_text); |
|
1204 | 1290 | if (render) { |
@@ -1224,7 +1310,7 b' var IPython = (function (IPython) {' | |||
|
1224 | 1310 | Notebook.prototype.collapse_output = function (index) { |
|
1225 | 1311 | var i = this.index_or_selected(index); |
|
1226 | 1312 | var cell = this.get_cell(i); |
|
1227 |
if (cell !== null && (cell instanceof |
|
|
1313 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
|
1228 | 1314 | cell.collapse_output(); |
|
1229 | 1315 | this.set_dirty(true); |
|
1230 | 1316 | } |
@@ -1237,7 +1323,7 b' var IPython = (function (IPython) {' | |||
|
1237 | 1323 | */ |
|
1238 | 1324 | Notebook.prototype.collapse_all_output = function () { |
|
1239 | 1325 | $.map(this.get_cells(), function (cell, i) { |
|
1240 |
if (cell instanceof |
|
|
1326 | if (cell instanceof codecell.CodeCell) { | |
|
1241 | 1327 | cell.collapse_output(); |
|
1242 | 1328 | } |
|
1243 | 1329 | }); |
@@ -1254,7 +1340,7 b' var IPython = (function (IPython) {' | |||
|
1254 | 1340 | Notebook.prototype.expand_output = function (index) { |
|
1255 | 1341 | var i = this.index_or_selected(index); |
|
1256 | 1342 | var cell = this.get_cell(i); |
|
1257 |
if (cell !== null && (cell instanceof |
|
|
1343 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
|
1258 | 1344 | cell.expand_output(); |
|
1259 | 1345 | this.set_dirty(true); |
|
1260 | 1346 | } |
@@ -1267,7 +1353,7 b' var IPython = (function (IPython) {' | |||
|
1267 | 1353 | */ |
|
1268 | 1354 | Notebook.prototype.expand_all_output = function () { |
|
1269 | 1355 | $.map(this.get_cells(), function (cell, i) { |
|
1270 |
if (cell instanceof |
|
|
1356 | if (cell instanceof codecell.CodeCell) { | |
|
1271 | 1357 | cell.expand_output(); |
|
1272 | 1358 | } |
|
1273 | 1359 | }); |
@@ -1284,7 +1370,7 b' var IPython = (function (IPython) {' | |||
|
1284 | 1370 | Notebook.prototype.clear_output = function (index) { |
|
1285 | 1371 | var i = this.index_or_selected(index); |
|
1286 | 1372 | var cell = this.get_cell(i); |
|
1287 |
if (cell !== null && (cell instanceof |
|
|
1373 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
|
1288 | 1374 | cell.clear_output(); |
|
1289 | 1375 | this.set_dirty(true); |
|
1290 | 1376 | } |
@@ -1297,7 +1383,7 b' var IPython = (function (IPython) {' | |||
|
1297 | 1383 | */ |
|
1298 | 1384 | Notebook.prototype.clear_all_output = function () { |
|
1299 | 1385 | $.map(this.get_cells(), function (cell, i) { |
|
1300 |
if (cell instanceof |
|
|
1386 | if (cell instanceof codecell.CodeCell) { | |
|
1301 | 1387 | cell.clear_output(); |
|
1302 | 1388 | } |
|
1303 | 1389 | }); |
@@ -1313,7 +1399,7 b' var IPython = (function (IPython) {' | |||
|
1313 | 1399 | Notebook.prototype.scroll_output = function (index) { |
|
1314 | 1400 | var i = this.index_or_selected(index); |
|
1315 | 1401 | var cell = this.get_cell(i); |
|
1316 |
if (cell !== null && (cell instanceof |
|
|
1402 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
|
1317 | 1403 | cell.scroll_output(); |
|
1318 | 1404 | this.set_dirty(true); |
|
1319 | 1405 | } |
@@ -1326,7 +1412,7 b' var IPython = (function (IPython) {' | |||
|
1326 | 1412 | */ |
|
1327 | 1413 | Notebook.prototype.scroll_all_output = function () { |
|
1328 | 1414 | $.map(this.get_cells(), function (cell, i) { |
|
1329 |
if (cell instanceof |
|
|
1415 | if (cell instanceof codecell.CodeCell) { | |
|
1330 | 1416 | cell.scroll_output(); |
|
1331 | 1417 | } |
|
1332 | 1418 | }); |
@@ -1342,7 +1428,7 b' var IPython = (function (IPython) {' | |||
|
1342 | 1428 | Notebook.prototype.toggle_output = function (index) { |
|
1343 | 1429 | var i = this.index_or_selected(index); |
|
1344 | 1430 | var cell = this.get_cell(i); |
|
1345 |
if (cell !== null && (cell instanceof |
|
|
1431 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
|
1346 | 1432 | cell.toggle_output(); |
|
1347 | 1433 | this.set_dirty(true); |
|
1348 | 1434 | } |
@@ -1355,7 +1441,7 b' var IPython = (function (IPython) {' | |||
|
1355 | 1441 | */ |
|
1356 | 1442 | Notebook.prototype.toggle_all_output = function () { |
|
1357 | 1443 | $.map(this.get_cells(), function (cell, i) { |
|
1358 |
if (cell instanceof |
|
|
1444 | if (cell instanceof codecell.CodeCell) { | |
|
1359 | 1445 | cell.toggle_output(); |
|
1360 | 1446 | } |
|
1361 | 1447 | }); |
@@ -1372,7 +1458,7 b' var IPython = (function (IPython) {' | |||
|
1372 | 1458 | Notebook.prototype.toggle_output_scroll = function (index) { |
|
1373 | 1459 | var i = this.index_or_selected(index); |
|
1374 | 1460 | var cell = this.get_cell(i); |
|
1375 |
if (cell !== null && (cell instanceof |
|
|
1461 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
|
1376 | 1462 | cell.toggle_output_scroll(); |
|
1377 | 1463 | this.set_dirty(true); |
|
1378 | 1464 | } |
@@ -1385,7 +1471,7 b' var IPython = (function (IPython) {' | |||
|
1385 | 1471 | */ |
|
1386 | 1472 | Notebook.prototype.toggle_all_output_scroll = function () { |
|
1387 | 1473 | $.map(this.get_cells(), function (cell, i) { |
|
1388 |
if (cell instanceof |
|
|
1474 | if (cell instanceof codecell.CodeCell) { | |
|
1389 | 1475 | cell.toggle_output_scroll(); |
|
1390 | 1476 | } |
|
1391 | 1477 | }); |
@@ -1404,6 +1490,34 b' var IPython = (function (IPython) {' | |||
|
1404 | 1490 | this.get_selected_cell().toggle_line_numbers(); |
|
1405 | 1491 | }; |
|
1406 | 1492 | |
|
1493 | /** | |
|
1494 | * Set the codemirror mode for all code cells, including the default for | |
|
1495 | * new code cells. | |
|
1496 | * | |
|
1497 | * @method set_codemirror_mode | |
|
1498 | */ | |
|
1499 | Notebook.prototype.set_codemirror_mode = function(newmode){ | |
|
1500 | if (newmode === this.codemirror_mode) { | |
|
1501 | return; | |
|
1502 | } | |
|
1503 | this.codemirror_mode = newmode; | |
|
1504 | codecell.CodeCell.options_default.cm_config.mode = newmode; | |
|
1505 | modename = newmode.name || newmode | |
|
1506 | ||
|
1507 | that = this; | |
|
1508 | CodeMirror.requireMode(modename, function(){ | |
|
1509 | $.map(that.get_cells(), function(cell, i) { | |
|
1510 | if (cell.cell_type === 'code'){ | |
|
1511 | cell.code_mirror.setOption('mode', newmode); | |
|
1512 | // This is currently redundant, because cm_config ends up as | |
|
1513 | // codemirror's own .options object, but I don't want to | |
|
1514 | // rely on that. | |
|
1515 | cell.cm_config.mode = newmode; | |
|
1516 | } | |
|
1517 | }); | |
|
1518 | }) | |
|
1519 | }; | |
|
1520 | ||
|
1407 | 1521 | // Session related things |
|
1408 | 1522 | |
|
1409 | 1523 | /** |
@@ -1411,9 +1525,54 b' var IPython = (function (IPython) {' | |||
|
1411 | 1525 | * |
|
1412 | 1526 | * @method start_session |
|
1413 | 1527 | */ |
|
1414 | Notebook.prototype.start_session = function () { | |
|
1415 | this.session = new IPython.Session(this, this.options); | |
|
1416 | this.session.start($.proxy(this._session_started, this)); | |
|
1528 | Notebook.prototype.start_session = function (kernel_name) { | |
|
1529 | var that = this; | |
|
1530 | if (kernel_name === undefined) { | |
|
1531 | kernel_name = this.default_kernel_name; | |
|
1532 | } | |
|
1533 | if (this._session_starting) { | |
|
1534 | throw new session.SessionAlreadyStarting(); | |
|
1535 | } | |
|
1536 | this._session_starting = true; | |
|
1537 | ||
|
1538 | if (this.session !== null) { | |
|
1539 | var s = this.session; | |
|
1540 | this.session = null; | |
|
1541 | // need to start the new session in a callback after delete, | |
|
1542 | // because javascript does not guarantee the ordering of AJAX requests (?!) | |
|
1543 | s.delete(function () { | |
|
1544 | // on successful delete, start new session | |
|
1545 | that._session_starting = false; | |
|
1546 | that.start_session(kernel_name); | |
|
1547 | }, function (jqXHR, status, error) { | |
|
1548 | // log the failed delete, but still create a new session | |
|
1549 | // 404 just means it was already deleted by someone else, | |
|
1550 | // but other errors are possible. | |
|
1551 | utils.log_ajax_error(jqXHR, status, error); | |
|
1552 | that._session_starting = false; | |
|
1553 | that.start_session(kernel_name); | |
|
1554 | } | |
|
1555 | ); | |
|
1556 | return; | |
|
1557 | } | |
|
1558 | ||
|
1559 | ||
|
1560 | ||
|
1561 | this.session = new session.Session({ | |
|
1562 | base_url: this.base_url, | |
|
1563 | ws_url: this.ws_url, | |
|
1564 | notebook_path: this.notebook_path, | |
|
1565 | notebook_name: this.notebook_name, | |
|
1566 | // For now, create all sessions with the 'python' kernel, which is the | |
|
1567 | // default. Later, the user will be able to select kernels. This is | |
|
1568 | // overridden if KernelManager.kernel_cmd is specified for the server. | |
|
1569 | kernel_name: kernel_name, | |
|
1570 | notebook: this}); | |
|
1571 | ||
|
1572 | this.session.start( | |
|
1573 | $.proxy(this._session_started, this), | |
|
1574 | $.proxy(this._session_start_failed, this) | |
|
1575 | ); | |
|
1417 | 1576 | }; |
|
1418 | 1577 | |
|
1419 | 1578 | |
@@ -1423,15 +1582,20 b' var IPython = (function (IPython) {' | |||
|
1423 | 1582 | * |
|
1424 | 1583 | */ |
|
1425 | 1584 | Notebook.prototype._session_started = function(){ |
|
1585 | this._session_starting = false; | |
|
1426 | 1586 | this.kernel = this.session.kernel; |
|
1427 | 1587 | var ncells = this.ncells(); |
|
1428 | 1588 | for (var i=0; i<ncells; i++) { |
|
1429 | 1589 | var cell = this.get_cell(i); |
|
1430 |
if (cell instanceof |
|
|
1590 | if (cell instanceof codecell.CodeCell) { | |
|
1431 | 1591 | cell.set_kernel(this.session.kernel); |
|
1432 | 1592 | } |
|
1433 | 1593 | } |
|
1434 | 1594 | }; |
|
1595 | Notebook.prototype._session_start_failed = function (jqxhr, status, error){ | |
|
1596 | this._session_starting = false; | |
|
1597 | utils.log_ajax_error(jqxhr, status, error); | |
|
1598 | }; | |
|
1435 | 1599 | |
|
1436 | 1600 | /** |
|
1437 | 1601 | * Prompt the user to restart the IPython kernel. |
@@ -1440,7 +1604,9 b' var IPython = (function (IPython) {' | |||
|
1440 | 1604 | */ |
|
1441 | 1605 | Notebook.prototype.restart_kernel = function () { |
|
1442 | 1606 | var that = this; |
|
1443 |
|
|
|
1607 | dialog.modal({ | |
|
1608 | notebook: this, | |
|
1609 | keyboard_manager: this.keyboard_manager, | |
|
1444 | 1610 | title : "Restart kernel or continue running?", |
|
1445 | 1611 | body : $("<p/>").text( |
|
1446 | 1612 | 'Do you want to restart the current kernel? You will lose all variables defined in it.' |
@@ -1633,6 +1799,13 b' var IPython = (function (IPython) {' | |||
|
1633 | 1799 | this.metadata = content.metadata; |
|
1634 | 1800 | this.notebook_name = data.name; |
|
1635 | 1801 | var trusted = true; |
|
1802 | ||
|
1803 | // Trigger an event changing the kernel spec - this will set the default | |
|
1804 | // codemirror mode | |
|
1805 | if (this.metadata.kernelspec !== undefined) { | |
|
1806 | this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec); | |
|
1807 | } | |
|
1808 | ||
|
1636 | 1809 | // Only handle 1 worksheet for now. |
|
1637 | 1810 | var worksheet = content.worksheets[0]; |
|
1638 | 1811 | if (worksheet !== undefined) { |
@@ -1660,10 +1833,12 b' var IPython = (function (IPython) {' | |||
|
1660 | 1833 | } |
|
1661 | 1834 | if (trusted != this.trusted) { |
|
1662 | 1835 | this.trusted = trusted; |
|
1663 |
|
|
|
1836 | this.events.trigger("trust_changed.Notebook", trusted); | |
|
1664 | 1837 | } |
|
1665 | 1838 | if (content.worksheets.length > 1) { |
|
1666 |
|
|
|
1839 | dialog.modal({ | |
|
1840 | notebook: this, | |
|
1841 | keyboard_manager: this.keyboard_manager, | |
|
1667 | 1842 | title : "Multiple worksheets", |
|
1668 | 1843 | body : "This notebook has " + data.worksheets.length + " worksheets, " + |
|
1669 | 1844 | "but this version of IPython can only handle the first. " + |
@@ -1705,7 +1880,7 b' var IPython = (function (IPython) {' | |||
|
1705 | 1880 | }; |
|
1706 | 1881 | if (trusted != this.trusted) { |
|
1707 | 1882 | this.trusted = trusted; |
|
1708 |
|
|
|
1883 | this.events.trigger("trust_changed.Notebook", trusted); | |
|
1709 | 1884 | } |
|
1710 | 1885 | return data; |
|
1711 | 1886 | }; |
@@ -1730,10 +1905,10 b' var IPython = (function (IPython) {' | |||
|
1730 | 1905 | that.save_notebook(); |
|
1731 | 1906 | } |
|
1732 | 1907 | }, interval); |
|
1733 |
|
|
|
1908 | this.events.trigger("autosave_enabled.Notebook", interval); | |
|
1734 | 1909 | } else { |
|
1735 | 1910 | this.autosave_timer = null; |
|
1736 |
|
|
|
1911 | this.events.trigger("autosave_disabled.Notebook"); | |
|
1737 | 1912 | } |
|
1738 | 1913 | }; |
|
1739 | 1914 | |
@@ -1748,6 +1923,8 b' var IPython = (function (IPython) {' | |||
|
1748 | 1923 | var model = {}; |
|
1749 | 1924 | model.name = this.notebook_name; |
|
1750 | 1925 | model.path = this.notebook_path; |
|
1926 | model.type = 'notebook'; | |
|
1927 | model.format = 'json'; | |
|
1751 | 1928 | model.content = this.toJSON(); |
|
1752 | 1929 | model.content.nbformat = this.nbformat; |
|
1753 | 1930 | model.content.nbformat_minor = this.nbformat_minor; |
@@ -1768,10 +1945,10 b' var IPython = (function (IPython) {' | |||
|
1768 | 1945 | settings[key] = extra_settings[key]; |
|
1769 | 1946 | } |
|
1770 | 1947 | } |
|
1771 |
|
|
|
1948 | this.events.trigger('notebook_saving.Notebook'); | |
|
1772 | 1949 | var url = utils.url_join_encode( |
|
1773 | 1950 | this.base_url, |
|
1774 |
'api/ |
|
|
1951 | 'api/contents', | |
|
1775 | 1952 | this.notebook_path, |
|
1776 | 1953 | this.notebook_name |
|
1777 | 1954 | ); |
@@ -1789,7 +1966,7 b' var IPython = (function (IPython) {' | |||
|
1789 | 1966 | */ |
|
1790 | 1967 | Notebook.prototype.save_notebook_success = function (start, data, status, xhr) { |
|
1791 | 1968 | this.set_dirty(false); |
|
1792 |
|
|
|
1969 | this.events.trigger('notebook_saved.Notebook'); | |
|
1793 | 1970 | this._update_autosave_interval(start); |
|
1794 | 1971 | if (this._checkpoint_after_save) { |
|
1795 | 1972 | this.create_checkpoint(); |
@@ -1826,7 +2003,7 b' var IPython = (function (IPython) {' | |||
|
1826 | 2003 | * @param {String} error HTTP error message |
|
1827 | 2004 | */ |
|
1828 | 2005 | Notebook.prototype.save_notebook_error = function (xhr, status, error) { |
|
1829 |
|
|
|
2006 | this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]); | |
|
1830 | 2007 | }; |
|
1831 | 2008 | |
|
1832 | 2009 | /** |
@@ -1851,7 +2028,9 b' var IPython = (function (IPython) {' | |||
|
1851 | 2028 | ); |
|
1852 | 2029 | |
|
1853 | 2030 | var nb = this; |
|
1854 |
|
|
|
2031 | dialog.modal({ | |
|
2032 | notebook: this, | |
|
2033 | keyboard_manager: this.keyboard_manager, | |
|
1855 | 2034 | title: "Trust this notebook?", |
|
1856 | 2035 | body: body, |
|
1857 | 2036 | |
@@ -1867,7 +2046,7 b' var IPython = (function (IPython) {' | |||
|
1867 | 2046 | cell.output_area.trusted = true; |
|
1868 | 2047 | } |
|
1869 | 2048 | } |
|
1870 |
|
|
|
2049 | this.events.on('notebook_saved.Notebook', function () { | |
|
1871 | 2050 | window.location.reload(); |
|
1872 | 2051 | }); |
|
1873 | 2052 | nb.save_notebook(); |
@@ -1902,7 +2081,7 b' var IPython = (function (IPython) {' | |||
|
1902 | 2081 | }; |
|
1903 | 2082 | var url = utils.url_join_encode( |
|
1904 | 2083 | base_url, |
|
1905 |
'api/ |
|
|
2084 | 'api/contents', | |
|
1906 | 2085 | path |
|
1907 | 2086 | ); |
|
1908 | 2087 | $.ajax(url,settings); |
@@ -1931,7 +2110,7 b' var IPython = (function (IPython) {' | |||
|
1931 | 2110 | }; |
|
1932 | 2111 | var url = utils.url_join_encode( |
|
1933 | 2112 | base_url, |
|
1934 |
'api/ |
|
|
2113 | 'api/contents', | |
|
1935 | 2114 | path |
|
1936 | 2115 | ); |
|
1937 | 2116 | $.ajax(url,settings); |
@@ -1953,10 +2132,10 b' var IPython = (function (IPython) {' | |||
|
1953 | 2132 | success : $.proxy(that.rename_success, this), |
|
1954 | 2133 | error : $.proxy(that.rename_error, this) |
|
1955 | 2134 | }; |
|
1956 |
|
|
|
2135 | this.events.trigger('rename_notebook.Notebook', data); | |
|
1957 | 2136 | var url = utils.url_join_encode( |
|
1958 | 2137 | this.base_url, |
|
1959 |
'api/ |
|
|
2138 | 'api/contents', | |
|
1960 | 2139 | this.notebook_path, |
|
1961 | 2140 | this.notebook_name |
|
1962 | 2141 | ); |
@@ -1974,7 +2153,7 b' var IPython = (function (IPython) {' | |||
|
1974 | 2153 | }; |
|
1975 | 2154 | var url = utils.url_join_encode( |
|
1976 | 2155 | this.base_url, |
|
1977 |
'api/ |
|
|
2156 | 'api/contents', | |
|
1978 | 2157 | this.notebook_path, |
|
1979 | 2158 | this.notebook_name |
|
1980 | 2159 | ); |
@@ -1986,32 +2165,33 b' var IPython = (function (IPython) {' | |||
|
1986 | 2165 | var name = this.notebook_name = json.name; |
|
1987 | 2166 | var path = json.path; |
|
1988 | 2167 | this.session.rename_notebook(name, path); |
|
1989 |
|
|
|
2168 | this.events.trigger('notebook_renamed.Notebook', json); | |
|
1990 | 2169 | }; |
|
1991 | 2170 | |
|
1992 | 2171 | Notebook.prototype.rename_error = function (xhr, status, error) { |
|
1993 | 2172 | var that = this; |
|
1994 | var dialog = $('<div/>').append( | |
|
1995 | $("<p/>").addClass("rename-message") | |
|
1996 | .text('This notebook name already exists.') | |
|
2173 | var dialog_body = $('<div/>').append( | |
|
2174 | $("<p/>").text('This notebook name already exists.') | |
|
1997 | 2175 | ); |
|
1998 |
|
|
|
1999 |
|
|
|
2176 | this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]); | |
|
2177 | dialog.modal({ | |
|
2178 | notebook: this, | |
|
2179 | keyboard_manager: this.keyboard_manager, | |
|
2000 | 2180 | title: "Notebook Rename Error!", |
|
2001 | body: dialog, | |
|
2181 | body: dialog_body, | |
|
2002 | 2182 | buttons : { |
|
2003 | 2183 | "Cancel": {}, |
|
2004 | 2184 | "OK": { |
|
2005 | 2185 | class: "btn-primary", |
|
2006 | 2186 | click: function () { |
|
2007 |
|
|
|
2187 | this.save_widget.rename_notebook({notebook:that}); | |
|
2008 | 2188 | }} |
|
2009 | 2189 | }, |
|
2010 | 2190 | open : function (event, ui) { |
|
2011 | 2191 | var that = $(this); |
|
2012 | 2192 | // Upon ENTER, click the OK button. |
|
2013 | 2193 | that.find('input[type="text"]').keydown(function (event, ui) { |
|
2014 |
if (event.which === |
|
|
2194 | if (event.which === this.keyboard.keycodes.enter) { | |
|
2015 | 2195 | that.find('.btn-primary').first().click(); |
|
2016 | 2196 | } |
|
2017 | 2197 | }); |
@@ -2039,10 +2219,10 b' var IPython = (function (IPython) {' | |||
|
2039 | 2219 | success : $.proxy(this.load_notebook_success,this), |
|
2040 | 2220 | error : $.proxy(this.load_notebook_error,this), |
|
2041 | 2221 | }; |
|
2042 |
|
|
|
2222 | this.events.trigger('notebook_loading.Notebook'); | |
|
2043 | 2223 | var url = utils.url_join_encode( |
|
2044 | 2224 | this.base_url, |
|
2045 |
'api/ |
|
|
2225 | 'api/contents', | |
|
2046 | 2226 | this.notebook_path, |
|
2047 | 2227 | this.notebook_name |
|
2048 | 2228 | ); |
@@ -2077,7 +2257,9 b' var IPython = (function (IPython) {' | |||
|
2077 | 2257 | "newer notebook format will be used and older versions of IPython " + |
|
2078 | 2258 | "may not be able to read it. To keep the older version, close the " + |
|
2079 | 2259 | "notebook without saving it."; |
|
2080 |
|
|
|
2260 | dialog.modal({ | |
|
2261 | notebook: this, | |
|
2262 | keyboard_manager: this.keyboard_manager, | |
|
2081 | 2263 | title : "Notebook converted", |
|
2082 | 2264 | body : msg, |
|
2083 | 2265 | buttons : { |
@@ -2094,7 +2276,9 b' var IPython = (function (IPython) {' | |||
|
2094 | 2276 | this_vs + ". You can still work with this notebook, but some features " + |
|
2095 | 2277 | "introduced in later notebook versions may not be available."; |
|
2096 | 2278 | |
|
2097 |
|
|
|
2279 | dialog.modal({ | |
|
2280 | notebook: this, | |
|
2281 | keyboard_manager: this.keyboard_manager, | |
|
2098 | 2282 | title : "Newer Notebook", |
|
2099 | 2283 | body : msg, |
|
2100 | 2284 | buttons : { |
@@ -2109,22 +2293,25 b' var IPython = (function (IPython) {' | |||
|
2109 | 2293 | // Create the session after the notebook is completely loaded to prevent |
|
2110 | 2294 | // code execution upon loading, which is a security risk. |
|
2111 | 2295 | if (this.session === null) { |
|
2112 | this.start_session(); | |
|
2296 | var kernelspec = this.metadata.kernelspec || {}; | |
|
2297 | var kernel_name = kernelspec.name || this.default_kernel_name; | |
|
2298 | ||
|
2299 | this.start_session(kernel_name); | |
|
2113 | 2300 | } |
|
2114 | 2301 | // load our checkpoint list |
|
2115 | 2302 | this.list_checkpoints(); |
|
2116 | 2303 | |
|
2117 | 2304 | // load toolbar state |
|
2118 | 2305 | if (this.metadata.celltoolbar) { |
|
2119 |
|
|
|
2120 |
|
|
|
2306 | celltoolbar.CellToolbar.global_show(); | |
|
2307 | celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar); | |
|
2121 | 2308 | } else { |
|
2122 |
|
|
|
2309 | celltoolbar.CellToolbar.global_hide(); | |
|
2123 | 2310 | } |
|
2124 | 2311 | |
|
2125 | 2312 | // now that we're fully loaded, it is safe to restore save functionality |
|
2126 | 2313 | delete(this.save_notebook); |
|
2127 |
|
|
|
2314 | this.events.trigger('notebook_loaded.Notebook'); | |
|
2128 | 2315 | }; |
|
2129 | 2316 | |
|
2130 | 2317 | /** |
@@ -2136,16 +2323,19 b' var IPython = (function (IPython) {' | |||
|
2136 | 2323 | * @param {String} error HTTP error message |
|
2137 | 2324 | */ |
|
2138 | 2325 | Notebook.prototype.load_notebook_error = function (xhr, status, error) { |
|
2139 |
|
|
|
2326 | this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]); | |
|
2327 | utils.log_ajax_error(xhr, status, error); | |
|
2140 | 2328 | var msg; |
|
2141 | 2329 | if (xhr.status === 400) { |
|
2142 | msg = error; | |
|
2330 | msg = escape(utils.ajax_error_msg(xhr)); | |
|
2143 | 2331 | } else if (xhr.status === 500) { |
|
2144 | 2332 | msg = "An unknown error occurred while loading this notebook. " + |
|
2145 | 2333 | "This version can load notebook formats " + |
|
2146 | "v" + this.nbformat + " or earlier."; | |
|
2334 | "v" + this.nbformat + " or earlier. See the server log for details."; | |
|
2147 | 2335 | } |
|
2148 |
|
|
|
2336 | dialog.modal({ | |
|
2337 | notebook: this, | |
|
2338 | keyboard_manager: this.keyboard_manager, | |
|
2149 | 2339 | title: "Error loading notebook", |
|
2150 | 2340 | body : msg, |
|
2151 | 2341 | buttons : { |
@@ -2196,7 +2386,7 b' var IPython = (function (IPython) {' | |||
|
2196 | 2386 | Notebook.prototype.list_checkpoints = function () { |
|
2197 | 2387 | var url = utils.url_join_encode( |
|
2198 | 2388 | this.base_url, |
|
2199 |
'api/ |
|
|
2389 | 'api/contents', | |
|
2200 | 2390 | this.notebook_path, |
|
2201 | 2391 | this.notebook_name, |
|
2202 | 2392 | 'checkpoints' |
@@ -2224,7 +2414,7 b' var IPython = (function (IPython) {' | |||
|
2224 | 2414 | } else { |
|
2225 | 2415 | this.last_checkpoint = null; |
|
2226 | 2416 | } |
|
2227 |
|
|
|
2417 | this.events.trigger('checkpoints_listed.Notebook', [data]); | |
|
2228 | 2418 | }; |
|
2229 | 2419 | |
|
2230 | 2420 | /** |
@@ -2236,7 +2426,7 b' var IPython = (function (IPython) {' | |||
|
2236 | 2426 | * @param {String} error_msg HTTP error message |
|
2237 | 2427 | */ |
|
2238 | 2428 | Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) { |
|
2239 |
|
|
|
2429 | this.events.trigger('list_checkpoints_failed.Notebook'); | |
|
2240 | 2430 | }; |
|
2241 | 2431 | |
|
2242 | 2432 | /** |
@@ -2247,7 +2437,7 b' var IPython = (function (IPython) {' | |||
|
2247 | 2437 | Notebook.prototype.create_checkpoint = function () { |
|
2248 | 2438 | var url = utils.url_join_encode( |
|
2249 | 2439 | this.base_url, |
|
2250 |
'api/ |
|
|
2440 | 'api/contents', | |
|
2251 | 2441 | this.notebook_path, |
|
2252 | 2442 | this.notebook_name, |
|
2253 | 2443 | 'checkpoints' |
@@ -2270,7 +2460,7 b' var IPython = (function (IPython) {' | |||
|
2270 | 2460 | Notebook.prototype.create_checkpoint_success = function (data, status, xhr) { |
|
2271 | 2461 | data = $.parseJSON(data); |
|
2272 | 2462 | this.add_checkpoint(data); |
|
2273 |
|
|
|
2463 | this.events.trigger('checkpoint_created.Notebook', data); | |
|
2274 | 2464 | }; |
|
2275 | 2465 | |
|
2276 | 2466 | /** |
@@ -2282,7 +2472,7 b' var IPython = (function (IPython) {' | |||
|
2282 | 2472 | * @param {String} error_msg HTTP error message |
|
2283 | 2473 | */ |
|
2284 | 2474 | Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) { |
|
2285 |
|
|
|
2475 | this.events.trigger('checkpoint_failed.Notebook'); | |
|
2286 | 2476 | }; |
|
2287 | 2477 | |
|
2288 | 2478 | Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) { |
@@ -2309,7 +2499,9 b' var IPython = (function (IPython) {' | |||
|
2309 | 2499 | ).css("text-align", "center") |
|
2310 | 2500 | ); |
|
2311 | 2501 | |
|
2312 |
|
|
|
2502 | dialog.modal({ | |
|
2503 | notebook: this, | |
|
2504 | keyboard_manager: this.keyboard_manager, | |
|
2313 | 2505 | title : "Revert notebook to checkpoint", |
|
2314 | 2506 | body : body, |
|
2315 | 2507 | buttons : { |
@@ -2331,10 +2523,10 b' var IPython = (function (IPython) {' | |||
|
2331 | 2523 | * @param {String} checkpoint ID |
|
2332 | 2524 | */ |
|
2333 | 2525 | Notebook.prototype.restore_checkpoint = function (checkpoint) { |
|
2334 |
|
|
|
2526 | this.events.trigger('notebook_restoring.Notebook', checkpoint); | |
|
2335 | 2527 | var url = utils.url_join_encode( |
|
2336 | 2528 | this.base_url, |
|
2337 |
'api/ |
|
|
2529 | 'api/contents', | |
|
2338 | 2530 | this.notebook_path, |
|
2339 | 2531 | this.notebook_name, |
|
2340 | 2532 | 'checkpoints', |
@@ -2356,7 +2548,7 b' var IPython = (function (IPython) {' | |||
|
2356 | 2548 | * @param {jqXHR} xhr jQuery Ajax object |
|
2357 | 2549 | */ |
|
2358 | 2550 | Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) { |
|
2359 |
|
|
|
2551 | this.events.trigger('checkpoint_restored.Notebook'); | |
|
2360 | 2552 | this.load_notebook(this.notebook_name, this.notebook_path); |
|
2361 | 2553 | }; |
|
2362 | 2554 | |
@@ -2369,7 +2561,7 b' var IPython = (function (IPython) {' | |||
|
2369 | 2561 | * @param {String} error_msg HTTP error message |
|
2370 | 2562 | */ |
|
2371 | 2563 | Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) { |
|
2372 |
|
|
|
2564 | this.events.trigger('checkpoint_restore_failed.Notebook'); | |
|
2373 | 2565 | }; |
|
2374 | 2566 | |
|
2375 | 2567 | /** |
@@ -2379,10 +2571,10 b' var IPython = (function (IPython) {' | |||
|
2379 | 2571 | * @param {String} checkpoint ID |
|
2380 | 2572 | */ |
|
2381 | 2573 | Notebook.prototype.delete_checkpoint = function (checkpoint) { |
|
2382 |
|
|
|
2574 | this.events.trigger('notebook_restoring.Notebook', checkpoint); | |
|
2383 | 2575 | var url = utils.url_join_encode( |
|
2384 | 2576 | this.base_url, |
|
2385 |
'api/ |
|
|
2577 | 'api/contents', | |
|
2386 | 2578 | this.notebook_path, |
|
2387 | 2579 | this.notebook_name, |
|
2388 | 2580 | 'checkpoints', |
@@ -2404,7 +2596,7 b' var IPython = (function (IPython) {' | |||
|
2404 | 2596 | * @param {jqXHR} xhr jQuery Ajax object |
|
2405 | 2597 | */ |
|
2406 | 2598 | Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) { |
|
2407 |
|
|
|
2599 | this.events.trigger('checkpoint_deleted.Notebook', data); | |
|
2408 | 2600 | this.load_notebook(this.notebook_name, this.notebook_path); |
|
2409 | 2601 | }; |
|
2410 | 2602 | |
@@ -2414,17 +2606,15 b' var IPython = (function (IPython) {' | |||
|
2414 | 2606 | * @method delete_checkpoint_error |
|
2415 | 2607 | * @param {jqXHR} xhr jQuery Ajax object |
|
2416 | 2608 | * @param {String} status Description of response status |
|
2417 |
* @param {String} error |
|
|
2609 | * @param {String} error HTTP error message | |
|
2418 | 2610 | */ |
|
2419 |
Notebook.prototype.delete_checkpoint_error = function (xhr, status, error |
|
|
2420 |
|
|
|
2611 | Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) { | |
|
2612 | this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]); | |
|
2421 | 2613 | }; |
|
2422 | 2614 | |
|
2423 | 2615 | |
|
2616 | // For backwards compatability. | |
|
2424 | 2617 | IPython.Notebook = Notebook; |
|
2425 | 2618 | |
|
2426 | ||
|
2427 | return IPython; | |
|
2428 | ||
|
2429 | }(IPython)); | |
|
2430 | ||
|
2619 | return {'Notebook': Notebook}; | |
|
2620 | }); |
@@ -1,21 +1,31 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2012 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 |
|
|
|
10 | //============================================================================ | |
|
11 | ||
|
12 | var IPython = (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | 'base/js/dialog', | |
|
9 | 'notebook/js/notificationwidget', | |
|
10 | 'moment' | |
|
11 | ], function(IPython, $, utils, dialog, notificationwidget, moment) { | |
|
13 | 12 | "use strict"; |
|
14 | var utils = IPython.utils; | |
|
15 | ||
|
16 | 13 | |
|
17 | var NotificationArea = function (selector) { | |
|
14 | var NotificationArea = function (selector, options) { | |
|
15 | // Constructor | |
|
16 | // | |
|
17 | // Parameters: | |
|
18 | // selector: string | |
|
19 | // options: dictionary | |
|
20 | // Dictionary of keyword arguments. | |
|
21 | // notebook: Notebook instance | |
|
22 | // events: $(Events) instance | |
|
23 | // save_widget: SaveWidget instance | |
|
18 | 24 | this.selector = selector; |
|
25 | this.events = options.events; | |
|
26 | this.save_widget = options.save_widget; | |
|
27 | this.notebook = options.notebook; | |
|
28 | this.keyboard_manager = options.keyboard_manager; | |
|
19 | 29 | if (this.selector !== undefined) { |
|
20 | 30 | this.element = $(selector); |
|
21 | 31 | } |
@@ -23,13 +33,10 b' var IPython = (function (IPython) {' | |||
|
23 | 33 | }; |
|
24 | 34 | |
|
25 | 35 | NotificationArea.prototype.temp_message = function (msg, timeout, css_class) { |
|
26 | var uuid = utils.uuid(); | |
|
27 | 36 | if( css_class == 'danger') {css_class = 'ui-state-error';} |
|
28 | 37 | if( css_class == 'warning') {css_class = 'ui-state-highlight';} |
|
29 | 38 | var tdiv = $('<div>') |
|
30 | .attr('id',uuid) | |
|
31 | .addClass('notification_widget ui-widget ui-widget-content ui-corner-all') | |
|
32 | .addClass('border-box-sizing') | |
|
39 | .addClass('notification_widget') | |
|
33 | 40 | .addClass(css_class) |
|
34 | 41 | .hide() |
|
35 | 42 | .text(msg); |
@@ -63,46 +70,53 b' var IPython = (function (IPython) {' | |||
|
63 | 70 | } |
|
64 | 71 | var div = $('<div/>').attr('id','notification_'+name); |
|
65 | 72 | $(this.selector).append(div); |
|
66 |
this.widget_dict[name] = new |
|
|
73 | this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name); | |
|
67 | 74 | return this.widget_dict[name]; |
|
68 | 75 | }; |
|
69 | 76 | |
|
70 | 77 | NotificationArea.prototype.init_notification_widgets = function() { |
|
78 | var that = this; | |
|
71 | 79 | var knw = this.new_notification_widget('kernel'); |
|
72 | 80 | var $kernel_ind_icon = $("#kernel_indicator_icon"); |
|
73 | 81 | var $modal_ind_icon = $("#modal_indicator_icon"); |
|
74 | 82 | |
|
75 | 83 | // Command/Edit mode |
|
76 |
|
|
|
77 |
|
|
|
84 | this.events.on('edit_mode.Notebook',function () { | |
|
85 | that.save_widget.update_document_title(); | |
|
78 | 86 | $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode'); |
|
79 | 87 | }); |
|
80 | 88 | |
|
81 |
|
|
|
82 |
|
|
|
89 | this.events.on('command_mode.Notebook',function () { | |
|
90 | that.save_widget.update_document_title(); | |
|
83 | 91 | $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode'); |
|
84 | 92 | }); |
|
85 | 93 | |
|
86 | 94 | // Implicitly start off in Command mode, switching to Edit mode will trigger event |
|
87 |
$modal_ind_icon.attr('class','command |
|
|
95 | $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode'); | |
|
88 | 96 | |
|
89 | 97 | // Kernel events |
|
90 |
|
|
|
91 |
|
|
|
98 | this.events.on('status_idle.Kernel',function () { | |
|
99 | that.save_widget.update_document_title(); | |
|
92 | 100 | $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle'); |
|
93 | 101 | }); |
|
94 | 102 | |
|
95 |
|
|
|
103 | this.events.on('status_busy.Kernel',function () { | |
|
96 | 104 | window.document.title='(Busy) '+window.document.title; |
|
97 | 105 | $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy'); |
|
98 | 106 | }); |
|
99 | 107 | |
|
100 |
|
|
|
101 |
|
|
|
108 | this.events.on('status_restarting.Kernel',function () { | |
|
109 | that.save_widget.update_document_title(); | |
|
102 | 110 | knw.set_message("Restarting kernel", 2000); |
|
103 | 111 | }); |
|
104 | 112 | |
|
105 |
|
|
|
113 | this.events.on('status_dead.Kernel',function () { | |
|
114 | that.save_widget.update_document_title(); | |
|
115 | knw.danger("Dead kernel"); | |
|
116 | $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead'); | |
|
117 | }); | |
|
118 | ||
|
119 | this.events.on('status_interrupting.Kernel',function () { | |
|
106 | 120 | knw.set_message("Interrupting kernel", 2000); |
|
107 | 121 | }); |
|
108 | 122 | |
@@ -110,28 +124,32 b' var IPython = (function (IPython) {' | |||
|
110 | 124 | // When the kernel_info reply arrives, the kernel is idle. |
|
111 | 125 | $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy'); |
|
112 | 126 | |
|
113 |
|
|
|
127 | this.events.on('status_started.Kernel', function (evt, data) { | |
|
128 | knw.info("Websockets Connected", 500); | |
|
129 | that.events.trigger('status_busy.Kernel'); | |
|
114 | 130 | data.kernel.kernel_info(function () { |
|
115 |
|
|
|
131 | that.events.trigger('status_idle.Kernel'); | |
|
116 | 132 | }); |
|
117 | 133 | }); |
|
118 | 134 | |
|
119 |
|
|
|
135 | this.events.on('status_dead.Kernel',function () { | |
|
120 | 136 | var msg = 'The kernel has died, and the automatic restart has failed.' + |
|
121 | 137 | ' It is possible the kernel cannot be restarted.' + |
|
122 | 138 | ' If you are not able to restart the kernel, you will still be able to save' + |
|
123 | 139 | ' the notebook, but running code will no longer work until the notebook' + |
|
124 | 140 | ' is reopened.'; |
|
125 | 141 | |
|
126 |
|
|
|
142 | dialog.modal({ | |
|
127 | 143 | title: "Dead kernel", |
|
128 | 144 | body : msg, |
|
145 | keyboard_manager: that.keyboard_manager, | |
|
146 | notebook: that.notebook, | |
|
129 | 147 | buttons : { |
|
130 | 148 | "Manual Restart": { |
|
131 | 149 | class: "btn-danger", |
|
132 | 150 | click: function () { |
|
133 |
|
|
|
134 |
|
|
|
151 | that.events.trigger('status_restarting.Kernel'); | |
|
152 | that.notebook.start_kernel(); | |
|
135 | 153 | } |
|
136 | 154 | }, |
|
137 | 155 | "Don't restart": {} |
@@ -139,13 +157,18 b' var IPython = (function (IPython) {' | |||
|
139 | 157 | }); |
|
140 | 158 | }); |
|
141 | 159 | |
|
142 |
|
|
|
160 | this.events.on('websocket_closed.Kernel', function (event, data) { | |
|
143 | 161 | var kernel = data.kernel; |
|
144 | 162 | var ws_url = data.ws_url; |
|
145 | 163 | var early = data.early; |
|
146 | 164 | var msg; |
|
165 | ||
|
166 | $kernel_ind_icon | |
|
167 | .attr('class', 'kernel_disconnected_icon') | |
|
168 | .attr('title', 'No Connection to Kernel'); | |
|
169 | ||
|
147 | 170 | if (!early) { |
|
148 |
knw. |
|
|
171 | knw.warning('Reconnecting'); | |
|
149 | 172 | setTimeout(function () { |
|
150 | 173 | kernel.start_channels(); |
|
151 | 174 | }, 5000); |
@@ -155,14 +178,16 b' var IPython = (function (IPython) {' | |||
|
155 | 178 | msg = "A WebSocket connection could not be established." + |
|
156 | 179 | " You will NOT be able to run code. Check your" + |
|
157 | 180 | " network connection or notebook server configuration."; |
|
158 |
|
|
|
181 | dialog.modal({ | |
|
159 | 182 | title: "WebSocket connection failed", |
|
160 | 183 | body: msg, |
|
184 | keyboard_manager: that.keyboard_manager, | |
|
185 | notebook: that.notebook, | |
|
161 | 186 | buttons : { |
|
162 | 187 | "OK": {}, |
|
163 | 188 | "Reconnect": { |
|
164 | 189 | click: function () { |
|
165 |
knw. |
|
|
190 | knw.warning('Reconnecting'); | |
|
166 | 191 | setTimeout(function () { |
|
167 | 192 | kernel.start_channels(); |
|
168 | 193 | }, 5000); |
@@ -176,52 +201,52 b' var IPython = (function (IPython) {' | |||
|
176 | 201 | var nnw = this.new_notification_widget('notebook'); |
|
177 | 202 | |
|
178 | 203 | // Notebook events |
|
179 |
|
|
|
204 | this.events.on('notebook_loading.Notebook', function () { | |
|
180 | 205 | nnw.set_message("Loading notebook",500); |
|
181 | 206 | }); |
|
182 |
|
|
|
207 | this.events.on('notebook_loaded.Notebook', function () { | |
|
183 | 208 | nnw.set_message("Notebook loaded",500); |
|
184 | 209 | }); |
|
185 |
|
|
|
210 | this.events.on('notebook_saving.Notebook', function () { | |
|
186 | 211 | nnw.set_message("Saving notebook",500); |
|
187 | 212 | }); |
|
188 |
|
|
|
213 | this.events.on('notebook_saved.Notebook', function () { | |
|
189 | 214 | nnw.set_message("Notebook saved",2000); |
|
190 | 215 | }); |
|
191 |
|
|
|
192 |
nnw. |
|
|
216 | this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) { | |
|
217 | nnw.warning(data || "Notebook save failed"); | |
|
193 | 218 | }); |
|
194 | 219 | |
|
195 | 220 | // Checkpoint events |
|
196 |
|
|
|
221 | this.events.on('checkpoint_created.Notebook', function (evt, data) { | |
|
197 | 222 | var msg = "Checkpoint created"; |
|
198 | 223 | if (data.last_modified) { |
|
199 | 224 | var d = new Date(data.last_modified); |
|
200 |
msg = msg + ": " + d.format("HH: |
|
|
225 | msg = msg + ": " + moment(d).format("HH:mm:ss"); | |
|
201 | 226 | } |
|
202 | 227 | nnw.set_message(msg, 2000); |
|
203 | 228 | }); |
|
204 |
|
|
|
205 |
nnw. |
|
|
229 | this.events.on('checkpoint_failed.Notebook', function () { | |
|
230 | nnw.warning("Checkpoint failed"); | |
|
206 | 231 | }); |
|
207 |
|
|
|
232 | this.events.on('checkpoint_deleted.Notebook', function () { | |
|
208 | 233 | nnw.set_message("Checkpoint deleted", 500); |
|
209 | 234 | }); |
|
210 |
|
|
|
211 |
nnw. |
|
|
235 | this.events.on('checkpoint_delete_failed.Notebook', function () { | |
|
236 | nnw.warning("Checkpoint delete failed"); | |
|
212 | 237 | }); |
|
213 |
|
|
|
238 | this.events.on('checkpoint_restoring.Notebook', function () { | |
|
214 | 239 | nnw.set_message("Restoring to checkpoint...", 500); |
|
215 | 240 | }); |
|
216 |
|
|
|
217 |
nnw. |
|
|
241 | this.events.on('checkpoint_restore_failed.Notebook', function () { | |
|
242 | nnw.warning("Checkpoint restore failed"); | |
|
218 | 243 | }); |
|
219 | 244 | |
|
220 | 245 | // Autosave events |
|
221 |
|
|
|
246 | this.events.on('autosave_disabled.Notebook', function () { | |
|
222 | 247 | nnw.set_message("Autosave disabled", 2000); |
|
223 | 248 | }); |
|
224 |
|
|
|
249 | this.events.on('autosave_enabled.Notebook', function (evt, interval) { | |
|
225 | 250 | nnw.set_message("Saving every " + interval / 1000 + "s", 1000); |
|
226 | 251 | }); |
|
227 | 252 | |
@@ -229,7 +254,5 b' var IPython = (function (IPython) {' | |||
|
229 | 254 | |
|
230 | 255 | IPython.NotificationArea = NotificationArea; |
|
231 | 256 | |
|
232 | return IPython; | |
|
233 | ||
|
234 | }(IPython)); | |
|
235 | ||
|
257 | return {'NotificationArea': NotificationArea}; | |
|
258 | }); |
@@ -1,18 +1,11 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // Notification widget | |
|
10 | //============================================================================ | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
11 | 3 | |
|
12 | var IPython = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | ], function(IPython, $) { | |
|
13 | 8 | "use strict"; |
|
14 | var utils = IPython.utils; | |
|
15 | ||
|
16 | 9 | |
|
17 | 10 | var NotificationWidget = function (selector) { |
|
18 | 11 | this.selector = selector; |
@@ -22,7 +15,6 b' var IPython = (function (IPython) {' | |||
|
22 | 15 | this.element = $(selector); |
|
23 | 16 | this.style(); |
|
24 | 17 | } |
|
25 | this.element.button(); | |
|
26 | 18 | this.element.hide(); |
|
27 | 19 | var that = this; |
|
28 | 20 | |
@@ -31,10 +23,8 b' var IPython = (function (IPython) {' | |||
|
31 | 23 | |
|
32 | 24 | }; |
|
33 | 25 | |
|
34 | ||
|
35 | 26 | NotificationWidget.prototype.style = function () { |
|
36 |
this.element.addClass('notification_widget |
|
|
37 | this.element.addClass('border-box-sizing'); | |
|
27 | this.element.addClass('notification_widget'); | |
|
38 | 28 | }; |
|
39 | 29 | |
|
40 | 30 | // msg : message to display |
@@ -43,14 +33,24 b' var IPython = (function (IPython) {' | |||
|
43 | 33 | // if timeout <= 0 |
|
44 | 34 | // click_callback : function called if user click on notification |
|
45 | 35 | // could return false to prevent the notification to be dismissed |
|
46 | NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, opts) { | |
|
47 | var opts = opts || {}; | |
|
48 |
var callback = click_callback || function() {return |
|
|
36 | NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) { | |
|
37 | var options = options || {}; | |
|
38 | var callback = click_callback || function() {return true;}; | |
|
49 | 39 | var that = this; |
|
50 | this.inner.attr('class', opts.icon); | |
|
51 | this.inner.attr('title', opts.title); | |
|
40 | // unbind potential previous callback | |
|
41 | this.element.unbind('click'); | |
|
42 | this.inner.attr('class', options.icon); | |
|
43 | this.inner.attr('title', options.title); | |
|
52 | 44 | this.inner.text(msg); |
|
53 | 45 | this.element.fadeIn(100); |
|
46 | ||
|
47 | // reset previous set style | |
|
48 | this.element.removeClass(); | |
|
49 | this.style(); | |
|
50 | if (options.class){ | |
|
51 | ||
|
52 | this.element.addClass(options.class) | |
|
53 | } | |
|
54 | 54 | if (this.timeout !== null) { |
|
55 | 55 | clearTimeout(this.timeout); |
|
56 | 56 | this.timeout = null; |
@@ -62,7 +62,7 b' var IPython = (function (IPython) {' | |||
|
62 | 62 | }, timeout); |
|
63 | 63 | } else { |
|
64 | 64 | this.element.click(function() { |
|
65 | if( callback() != false ) { | |
|
65 | if( callback() !== false ) { | |
|
66 | 66 | that.element.fadeOut(100, function () {that.inner.text('');}); |
|
67 | 67 | that.element.unbind('click'); |
|
68 | 68 | } |
@@ -75,14 +75,30 b' var IPython = (function (IPython) {' | |||
|
75 | 75 | }; |
|
76 | 76 | |
|
77 | 77 | |
|
78 | NotificationWidget.prototype.info = function (msg, timeout, click_callback, options) { | |
|
79 | var options = options || {}; | |
|
80 | options.class = options.class +' info'; | |
|
81 | var timeout = timeout || 3500; | |
|
82 | this.set_message(msg, timeout, click_callback, options); | |
|
83 | } | |
|
84 | NotificationWidget.prototype.warning = function (msg, timeout, click_callback, options) { | |
|
85 | var options = options || {}; | |
|
86 | options.class = options.class +' warning'; | |
|
87 | this.set_message(msg, timeout, click_callback, options); | |
|
88 | } | |
|
89 | NotificationWidget.prototype.danger = function (msg, timeout, click_callback, options) { | |
|
90 | var options = options || {}; | |
|
91 | options.class = options.class +' danger'; | |
|
92 | this.set_message(msg, timeout, click_callback, options); | |
|
93 | } | |
|
94 | ||
|
95 | ||
|
78 | 96 | NotificationWidget.prototype.get_message = function () { |
|
79 | 97 | return this.inner.html(); |
|
80 | 98 | }; |
|
81 | 99 | |
|
82 | ||
|
100 | // For backwards compatibility. | |
|
83 | 101 | IPython.NotificationWidget = NotificationWidget; |
|
84 | 102 | |
|
85 | return IPython; | |
|
86 | ||
|
87 | }(IPython)); | |
|
88 | ||
|
103 | return {'NotificationWidget': NotificationWidget}; | |
|
104 | }); |
@@ -1,38 +1,37 b'' | |||
|
1 | 1 | // Copyright (c) IPython Development Team. |
|
2 | 2 | // Distributed under the terms of the Modified BSD License. |
|
3 | 3 | |
|
4 | //============================================================================ | |
|
5 | // OutputArea | |
|
6 | //============================================================================ | |
|
7 | ||
|
8 | /** | |
|
9 | * @module IPython | |
|
10 | * @namespace IPython | |
|
11 | * @submodule OutputArea | |
|
12 | */ | |
|
13 | var IPython = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jqueryui', | |
|
7 | 'base/js/utils', | |
|
8 | 'base/js/security', | |
|
9 | 'base/js/keyboard', | |
|
10 | 'notebook/js/mathjaxutils', | |
|
11 | 'components/marked/lib/marked', | |
|
12 | ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) { | |
|
14 | 13 | "use strict"; |
|
15 | 14 | |
|
16 | var utils = IPython.utils; | |
|
17 | ||
|
18 | 15 | /** |
|
19 | 16 | * @class OutputArea |
|
20 | 17 | * |
|
21 | 18 | * @constructor |
|
22 | 19 | */ |
|
23 | 20 | |
|
24 |
var OutputArea = function ( |
|
|
25 | this.selector = selector; | |
|
26 |
this. |
|
|
21 | var OutputArea = function (options) { | |
|
22 | this.selector = options.selector; | |
|
23 | this.events = options.events; | |
|
24 | this.keyboard_manager = options.keyboard_manager; | |
|
25 | this.wrapper = $(options.selector); | |
|
27 | 26 | this.outputs = []; |
|
28 | 27 | this.collapsed = false; |
|
29 | 28 | this.scrolled = false; |
|
30 | 29 | this.trusted = true; |
|
31 | 30 | this.clear_queued = null; |
|
32 | if (prompt_area === undefined) { | |
|
31 | if (options.prompt_area === undefined) { | |
|
33 | 32 | this.prompt_area = true; |
|
34 | 33 | } else { |
|
35 | this.prompt_area = prompt_area; | |
|
34 | this.prompt_area = options.prompt_area; | |
|
36 | 35 | } |
|
37 | 36 | this.create_elements(); |
|
38 | 37 | this.style(); |
@@ -101,7 +100,7 b' var IPython = (function (IPython) {' | |||
|
101 | 100 | |
|
102 | 101 | this.element.resize(function () { |
|
103 | 102 | // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled |
|
104 |
if ( |
|
|
103 | if ( utils.browser[0] === "Firefox" ) { | |
|
105 | 104 | return; |
|
106 | 105 | } |
|
107 | 106 | // maybe scroll output, |
@@ -282,12 +281,15 b' var IPython = (function (IPython) {' | |||
|
282 | 281 | needs_height_reset = true; |
|
283 | 282 | } |
|
284 | 283 | |
|
284 | var record_output = true; | |
|
285 | ||
|
285 | 286 | if (json.output_type === 'execute_result') { |
|
286 | 287 | this.append_execute_result(json); |
|
287 | 288 | } else if (json.output_type === 'error') { |
|
288 | 289 | this.append_error(json); |
|
289 | 290 | } else if (json.output_type === 'stream') { |
|
290 | this.append_stream(json); | |
|
291 | // append_stream might have merged the output with earlier stream output | |
|
292 | record_output = this.append_stream(json); | |
|
291 | 293 | } |
|
292 | 294 | |
|
293 | 295 | // We must release the animation fixed height in a callback since Gecko |
@@ -308,7 +310,9 b' var IPython = (function (IPython) {' | |||
|
308 | 310 | handle_appended(); |
|
309 | 311 | } |
|
310 | 312 | |
|
313 | if (record_output) { | |
|
311 | 314 | this.outputs.push(json); |
|
315 | } | |
|
312 | 316 | }; |
|
313 | 317 | |
|
314 | 318 | |
@@ -459,20 +463,23 b' var IPython = (function (IPython) {' | |||
|
459 | 463 | // latest output was in the same stream, |
|
460 | 464 | // so append directly into its pre tag |
|
461 | 465 | // escape ANSI & HTML specials: |
|
466 | last.text = utils.fixCarriageReturn(last.text + json.text); | |
|
462 | 467 | var pre = this.element.find('div.'+subclass).last().find('pre'); |
|
463 |
var html = utils.fixC |
|
|
464 | pre.html() + utils.fixConsole(text)); | |
|
468 | var html = utils.fixConsole(last.text); | |
|
465 | 469 | // The only user content injected with this HTML call is |
|
466 | 470 | // escaped by the fixConsole() method. |
|
467 | 471 | pre.html(html); |
|
468 | return; | |
|
472 | // return false signals that we merged this output with the previous one, | |
|
473 | // and the new output shouldn't be recorded. | |
|
474 | return false; | |
|
469 | 475 | } |
|
470 | 476 | } |
|
471 | 477 | |
|
472 | 478 | if (!text.replace("\r", "")) { |
|
473 | 479 | // text is nothing (empty string, \r, etc.) |
|
474 | 480 | // so don't append any elements, which might add undesirable space |
|
475 | return; | |
|
481 | // return true to indicate the output should be recorded. | |
|
482 | return true; | |
|
476 | 483 | } |
|
477 | 484 | |
|
478 | 485 | // If we got here, attach a new div |
@@ -482,6 +489,7 b' var IPython = (function (IPython) {' | |||
|
482 | 489 | append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass); |
|
483 | 490 | } |
|
484 | 491 | this._safe_append(toinsert); |
|
492 | return true; | |
|
485 | 493 | }; |
|
486 | 494 | |
|
487 | 495 | |
@@ -515,7 +523,7 b' var IPython = (function (IPython) {' | |||
|
515 | 523 | if (!this.trusted && !OutputArea.safe_outputs[type]) { |
|
516 | 524 | // not trusted, sanitize HTML |
|
517 | 525 | if (type==='text/html' || type==='text/svg') { |
|
518 |
value = |
|
|
526 | value = security.sanitize_html(value); | |
|
519 | 527 | } else { |
|
520 | 528 | // don't display if we don't know how to sanitize it |
|
521 | 529 | console.log("Ignoring untrusted " + type + " output."); |
@@ -531,7 +539,7 b' var IPython = (function (IPython) {' | |||
|
531 | 539 | if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) { |
|
532 | 540 | setTimeout(handle_inserted, 0); |
|
533 | 541 | } |
|
534 |
|
|
|
542 | this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]); | |
|
535 | 543 | return toinsert; |
|
536 | 544 | } |
|
537 | 545 | } |
@@ -542,7 +550,7 b' var IPython = (function (IPython) {' | |||
|
542 | 550 | var append_html = function (html, md, element) { |
|
543 | 551 | var type = 'text/html'; |
|
544 | 552 | var toinsert = this.create_output_subarea(md, "output_html rendered_html", type); |
|
545 |
|
|
|
553 | this.keyboard_manager.register_events(toinsert); | |
|
546 | 554 | toinsert.append(html); |
|
547 | 555 | element.append(toinsert); |
|
548 | 556 | return toinsert; |
@@ -552,11 +560,11 b' var IPython = (function (IPython) {' | |||
|
552 | 560 | var append_markdown = function(markdown, md, element) { |
|
553 | 561 | var type = 'text/markdown'; |
|
554 | 562 | var toinsert = this.create_output_subarea(md, "output_markdown", type); |
|
555 |
var text_and_math = |
|
|
563 | var text_and_math = mathjaxutils.remove_math(markdown); | |
|
556 | 564 | var text = text_and_math[0]; |
|
557 | 565 | var math = text_and_math[1]; |
|
558 | 566 | var html = marked.parser(marked.lexer(text)); |
|
559 |
html = |
|
|
567 | html = mathjaxutils.replace_math(html, math); | |
|
560 | 568 | toinsert.append(html); |
|
561 | 569 | element.append(toinsert); |
|
562 | 570 | return toinsert; |
@@ -567,13 +575,8 b' var IPython = (function (IPython) {' | |||
|
567 | 575 | // We just eval the JS code, element appears in the local scope. |
|
568 | 576 | var type = 'application/javascript'; |
|
569 | 577 | var toinsert = this.create_output_subarea(md, "output_javascript", type); |
|
570 |
|
|
|
578 | this.keyboard_manager.register_events(toinsert); | |
|
571 | 579 | element.append(toinsert); |
|
572 | // FIXME TODO : remove `container element for 3.0` | |
|
573 | //backward compat, js should be eval'ed in a context where `container` is defined. | |
|
574 | var container = element; | |
|
575 | container.show = function(){console.log('Warning "container.show()" is deprecated.')}; | |
|
576 | // end backward compat | |
|
577 | 580 | |
|
578 | 581 | // Fix for ipython/issues/5293, make sure `element` is the area which |
|
579 | 582 | // output can be inserted into at the time of JS execution. |
@@ -763,7 +766,7 b' var IPython = (function (IPython) {' | |||
|
763 | 766 | .keydown(function (event, ui) { |
|
764 | 767 | // make sure we submit on enter, |
|
765 | 768 | // and don't re-execute the *cell* on shift-enter |
|
766 |
if (event.which === |
|
|
769 | if (event.which === keyboard.keycodes.enter) { | |
|
767 | 770 | that._submit_raw_input(); |
|
768 | 771 | return false; |
|
769 | 772 | } |
@@ -775,7 +778,7 b' var IPython = (function (IPython) {' | |||
|
775 | 778 | var raw_input = area.find('input.raw_input'); |
|
776 | 779 | // Register events that enable/disable the keyboard manager while raw |
|
777 | 780 | // input is focused. |
|
778 |
|
|
|
781 | this.keyboard_manager.register_events(raw_input); | |
|
779 | 782 | // Note, the following line used to read raw_input.focus().focus(). |
|
780 | 783 | // This seemed to be needed otherwise only the cell would be focused. |
|
781 | 784 | // But with the modal UI, this seems to work fine with one call to focus(). |
@@ -794,14 +797,14 b' var IPython = (function (IPython) {' | |||
|
794 | 797 | } |
|
795 | 798 | var content = { |
|
796 | 799 | output_type : 'stream', |
|
797 |
|
|
|
800 | stream : 'stdout', | |
|
798 | 801 | text : theprompt.text() + echo + '\n' |
|
799 | 802 | } |
|
800 | 803 | // remove form container |
|
801 | 804 | container.parent().remove(); |
|
802 | 805 | // replace with plaintext version in stdout |
|
803 | 806 | this.append_output(content, false); |
|
804 |
|
|
|
807 | this.events.trigger('send_input_reply.Kernel', value); | |
|
805 | 808 | } |
|
806 | 809 | |
|
807 | 810 | |
@@ -992,8 +995,8 b' var IPython = (function (IPython) {' | |||
|
992 | 995 | "application/pdf" : append_pdf |
|
993 | 996 | }; |
|
994 | 997 | |
|
998 | // For backwards compatability. | |
|
995 | 999 | IPython.OutputArea = OutputArea; |
|
996 | 1000 | |
|
997 | return IPython; | |
|
998 | ||
|
999 | }(IPython)); | |
|
1001 | return {'OutputArea': OutputArea}; | |
|
1002 | }); |
@@ -1,20 +1,29 b'' | |||
|
1 | 1 | // Copyright (c) IPython Development Team. |
|
2 | 2 | // Distributed under the terms of the Modified BSD License. |
|
3 | 3 | |
|
4 | //============================================================================ | |
|
5 | // Pager | |
|
6 | //============================================================================ | |
|
7 | ||
|
8 | var IPython = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jqueryui', | |
|
7 | 'base/js/utils', | |
|
8 | ], function(IPython, $, utils) { | |
|
9 | 9 | "use strict"; |
|
10 | 10 | |
|
11 | var utils = IPython.utils; | |
|
12 | ||
|
13 | var Pager = function (pager_selector, pager_splitter_selector) { | |
|
11 | var Pager = function (pager_selector, pager_splitter_selector, options) { | |
|
12 | // Constructor | |
|
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 | 22 | this.pager_element = $(pager_selector); |
|
15 | 23 | this.pager_button_area = $('#pager_button_area'); |
|
16 | 24 | var that = this; |
|
17 | 25 | this.percentage_height = 0.40; |
|
26 | options.layout_manager.pager = this; | |
|
18 | 27 | this.pager_splitter_element = $(pager_splitter_selector) |
|
19 | 28 | .draggable({ |
|
20 | 29 | containment: 'window', |
@@ -23,7 +32,7 b' var IPython = (function (IPython) {' | |||
|
23 | 32 | drag: function(event, ui) { |
|
24 | 33 | // recalculate the amount of space the pager should take |
|
25 | 34 | var pheight = ($(document.body).height()-event.clientY-4); |
|
26 |
var downprct = pheight/ |
|
|
35 | var downprct = pheight/options.layout_manager.app_height(); | |
|
27 | 36 | downprct = Math.min(0.9, downprct); |
|
28 | 37 | if (downprct < 0.1) { |
|
29 | 38 | that.percentage_height = 0.1; |
@@ -32,7 +41,7 b' var IPython = (function (IPython) {' | |||
|
32 | 41 | that.percentage_height = downprct; |
|
33 | 42 | that.expand({'duration':0}); |
|
34 | 43 | } |
|
35 |
|
|
|
44 | options.layout_manager.do_resize(); | |
|
36 | 45 | } |
|
37 | 46 | }); |
|
38 | 47 | this.expanded = false; |
@@ -47,28 +56,26 b' var IPython = (function (IPython) {' | |||
|
47 | 56 | $('<a>').attr('role', "button") |
|
48 | 57 | .attr('title',"Open the pager in an external window") |
|
49 | 58 | .addClass('ui-button') |
|
50 | .click(function(){that.detach()}) | |
|
59 | .click(function(){that.detach();}) | |
|
51 | 60 | .attr('style','position: absolute; right: 20px;') |
|
52 | 61 | .append( |
|
53 | 62 | $('<span>').addClass("ui-icon ui-icon-extlink") |
|
54 | 63 | ) |
|
55 | ) | |
|
64 | ); | |
|
56 | 65 | this.pager_button_area.append( |
|
57 | 66 | $('<a>').attr('role', "button") |
|
58 | 67 | .attr('title',"Close the pager") |
|
59 | 68 | .addClass('ui-button') |
|
60 | .click(function(){that.collapse()}) | |
|
69 | .click(function(){that.collapse();}) | |
|
61 | 70 | .attr('style','position: absolute; right: 5px;') |
|
62 | 71 | .append( |
|
63 | 72 | $('<span>').addClass("ui-icon ui-icon-close") |
|
64 | 73 | ) |
|
65 | ) | |
|
74 | ); | |
|
66 | 75 | }; |
|
67 | 76 | |
|
68 | 77 | Pager.prototype.style = function () { |
|
69 |
this.pager_splitter_element.addClass(' |
|
|
70 | this.pager_element.addClass('border-box-sizing'); | |
|
71 | this.pager_element.find(".container").addClass('border-box-sizing'); | |
|
78 | this.pager_splitter_element.addClass('ui-widget ui-state-default'); | |
|
72 | 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 | 112 | that.toggle(); |
|
106 | 113 | }); |
|
107 | 114 | |
|
108 |
|
|
|
115 | this.events.on('open_with_text.Pager', function (event, payload) { | |
|
109 | 116 | // FIXME: support other mime types |
|
110 | 117 | if (payload.data['text/plain'] && payload.data['text/plain'] !== "") { |
|
111 | 118 | that.clear(); |
@@ -171,10 +178,8 b' var IPython = (function (IPython) {' | |||
|
171 | 178 | this.pager_element.find(".container").append($('<pre/>').html(utils.fixCarriageReturn(utils.fixConsole(text)))); |
|
172 | 179 | }; |
|
173 | 180 | |
|
174 | ||
|
181 | // Backwards compatability. | |
|
175 | 182 | IPython.Pager = Pager; |
|
176 | 183 | |
|
177 | return IPython; | |
|
178 | ||
|
179 | }(IPython)); | |
|
180 | ||
|
184 | return {'Pager': Pager}; | |
|
185 | }); |
@@ -1,16 +1,28 b'' | |||
|
1 | 1 | // Copyright (c) IPython Development Team. |
|
2 | 2 | // Distributed under the terms of the Modified BSD License. |
|
3 | 3 | |
|
4 | //============================================================================ | |
|
5 | // QuickHelp button | |
|
6 | //============================================================================ | |
|
7 | ||
|
8 | var IPython = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | 'base/js/dialog', | |
|
9 | ], function(IPython, $, utils, dialog) { | |
|
9 | 10 | "use strict"; |
|
10 | ||
|
11 | var platform = IPython.utils.platform; | |
|
12 | ||
|
13 | var QuickHelp = function (selector) { | |
|
11 | var platform = utils.platform; | |
|
12 | ||
|
13 | var QuickHelp = function (options) { | |
|
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 | 28 | var cmd_ctrl = 'Ctrl-'; |
@@ -70,8 +82,8 b' var IPython = (function (IPython) {' | |||
|
70 | 82 | $(this.shortcut_dialog).modal("toggle"); |
|
71 | 83 | return; |
|
72 | 84 | } |
|
73 |
var command_shortcuts = |
|
|
74 |
var edit_shortcuts = |
|
|
85 | var command_shortcuts = this.keyboard_manager.command_shortcuts.help(); | |
|
86 | var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help(); | |
|
75 | 87 | var help, shortcut; |
|
76 | 88 | var i, half, n; |
|
77 | 89 | var element = $('<div/>'); |
@@ -96,21 +108,23 b' var IPython = (function (IPython) {' | |||
|
96 | 108 | var edit_div = this.build_edit_help(cm_shortcuts); |
|
97 | 109 | element.append(edit_div); |
|
98 | 110 | |
|
99 |
this.shortcut_dialog = |
|
|
111 | this.shortcut_dialog = dialog.modal({ | |
|
100 | 112 | title : "Keyboard shortcuts", |
|
101 | 113 | body : element, |
|
102 | 114 | destroy : false, |
|
103 | 115 | buttons : { |
|
104 | 116 | Close : {} |
|
105 | } | |
|
117 | }, | |
|
118 | notebook: this.notebook, | |
|
119 | keyboard_manager: this.keyboard_manager, | |
|
106 | 120 | }); |
|
107 | 121 | this.shortcut_dialog.addClass("modal_stretch"); |
|
108 | 122 | |
|
109 |
|
|
|
123 | this.events.on('rebuild.QuickHelp', function() { that.force_rebuild = true;}); | |
|
110 | 124 | }; |
|
111 | 125 | |
|
112 | 126 | QuickHelp.prototype.build_command_help = function () { |
|
113 |
var command_shortcuts = |
|
|
127 | var command_shortcuts = this.keyboard_manager.command_shortcuts.help(); | |
|
114 | 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 | 150 | QuickHelp.prototype.build_edit_help = function (cm_shortcuts) { |
|
137 |
var edit_shortcuts = |
|
|
151 | var edit_shortcuts = this.keyboard_manager.edit_shortcuts.help(); | |
|
138 | 152 | jQuery.merge(cm_shortcuts, edit_shortcuts); |
|
139 | 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 | 177 | return div; |
|
164 | 178 | }; |
|
165 | 179 | |
|
166 | // Set module variables | |
|
180 | // Backwards compatability. | |
|
167 | 181 | IPython.QuickHelp = QuickHelp; |
|
168 | 182 | |
|
169 | return IPython; | |
|
170 | ||
|
171 | }(IPython)); | |
|
183 | return {'QuickHelp': QuickHelp}; | |
|
184 | }); |
@@ -1,33 +1,30 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008-2011 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // SaveWidget | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | var IPython = (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | 'base/js/dialog', | |
|
9 | 'base/js/keyboard', | |
|
10 | 'moment', | |
|
11 | ], function(IPython, $, utils, dialog, keyboard, moment) { | |
|
13 | 12 | "use strict"; |
|
14 | 13 | |
|
15 | var utils = IPython.utils; | |
|
16 | ||
|
17 | var SaveWidget = function (selector) { | |
|
14 | var SaveWidget = function (selector, options) { | |
|
15 | // TODO: Remove circular ref. | |
|
16 | this.notebook = undefined; | |
|
18 | 17 | this.selector = selector; |
|
18 | this.events = options.events; | |
|
19 | this._checkpoint_date = undefined; | |
|
20 | this.keyboard_manager = options.keyboard_manager; | |
|
19 | 21 | if (this.selector !== undefined) { |
|
20 | 22 | this.element = $(selector); |
|
21 | this.style(); | |
|
22 | 23 | this.bind_events(); |
|
23 | 24 | } |
|
24 | 25 | }; |
|
25 | 26 | |
|
26 | 27 | |
|
27 | SaveWidget.prototype.style = function () { | |
|
28 | }; | |
|
29 | ||
|
30 | ||
|
31 | 28 | SaveWidget.prototype.bind_events = function () { |
|
32 | 29 | var that = this; |
|
33 | 30 | this.element.find('span#notebook_name').click(function () { |
@@ -38,56 +35,59 b' var IPython = (function (IPython) {' | |||
|
38 | 35 | }, function () { |
|
39 | 36 | $(this).removeClass("ui-state-hover"); |
|
40 | 37 | }); |
|
41 |
|
|
|
38 | this.events.on('notebook_loaded.Notebook', function () { | |
|
42 | 39 | that.update_notebook_name(); |
|
43 | 40 | that.update_document_title(); |
|
44 | 41 | }); |
|
45 |
|
|
|
42 | this.events.on('notebook_saved.Notebook', function () { | |
|
46 | 43 | that.update_notebook_name(); |
|
47 | 44 | that.update_document_title(); |
|
48 | 45 | }); |
|
49 |
|
|
|
46 | this.events.on('notebook_renamed.Notebook', function () { | |
|
50 | 47 | that.update_notebook_name(); |
|
51 | 48 | that.update_document_title(); |
|
52 | 49 | that.update_address_bar(); |
|
53 | 50 | }); |
|
54 |
|
|
|
51 | this.events.on('notebook_save_failed.Notebook', function () { | |
|
55 | 52 | that.set_save_status('Autosave Failed!'); |
|
56 | 53 | }); |
|
57 |
|
|
|
58 | that.set_last_checkpoint(data[0]); | |
|
54 | this.events.on('checkpoints_listed.Notebook', function (event, data) { | |
|
55 | that._set_last_checkpoint(data[0]); | |
|
59 | 56 | }); |
|
60 | 57 | |
|
61 |
|
|
|
62 | that.set_last_checkpoint(data); | |
|
58 | this.events.on('checkpoint_created.Notebook', function (event, data) { | |
|
59 | that._set_last_checkpoint(data); | |
|
63 | 60 | }); |
|
64 |
|
|
|
61 | this.events.on('set_dirty.Notebook', function (event, data) { | |
|
65 | 62 | that.set_autosaved(data.value); |
|
66 | 63 | }); |
|
67 | 64 | }; |
|
68 | 65 | |
|
69 | 66 | |
|
70 | SaveWidget.prototype.rename_notebook = function () { | |
|
67 | SaveWidget.prototype.rename_notebook = function (options) { | |
|
68 | options = options || {}; | |
|
71 | 69 | var that = this; |
|
72 | var dialog = $('<div/>').append( | |
|
70 | var dialog_body = $('<div/>').append( | |
|
73 | 71 | $("<p/>").addClass("rename-message") |
|
74 | 72 | .text('Enter a new notebook name:') |
|
75 | 73 | ).append( |
|
76 | 74 | $("<br/>") |
|
77 | 75 | ).append( |
|
78 | 76 | $('<input/>').attr('type','text').attr('size','25').addClass('form-control') |
|
79 |
.val( |
|
|
77 | .val(that.notebook.get_notebook_name()) | |
|
80 | 78 | ); |
|
81 |
|
|
|
79 | dialog.modal({ | |
|
82 | 80 | title: "Rename Notebook", |
|
83 | body: dialog, | |
|
81 | body: dialog_body, | |
|
82 | notebook: options.notebook, | |
|
83 | keyboard_manager: this.keyboard_manager, | |
|
84 | 84 | buttons : { |
|
85 | 85 | "Cancel": {}, |
|
86 | 86 | "OK": { |
|
87 | 87 | class: "btn-primary", |
|
88 | 88 | click: function () { |
|
89 | 89 | var new_name = $(this).find('input').val(); |
|
90 |
if (! |
|
|
90 | if (!that.notebook.test_notebook_name(new_name)) { | |
|
91 | 91 | $(this).find('.rename-message').text( |
|
92 | 92 | "Invalid notebook name. Notebook names must "+ |
|
93 | 93 | "have 1 or more characters and can contain any characters " + |
@@ -95,7 +95,7 b' var IPython = (function (IPython) {' | |||
|
95 | 95 | ); |
|
96 | 96 | return false; |
|
97 | 97 | } else { |
|
98 |
|
|
|
98 | that.notebook.rename(new_name); | |
|
99 | 99 | } |
|
100 | 100 | }} |
|
101 | 101 | }, |
@@ -103,7 +103,7 b' var IPython = (function (IPython) {' | |||
|
103 | 103 | var that = $(this); |
|
104 | 104 | // Upon ENTER, click the OK button. |
|
105 | 105 | that.find('input[type="text"]').keydown(function (event, ui) { |
|
106 |
if (event.which === |
|
|
106 | if (event.which === keyboard.keycodes.enter) { | |
|
107 | 107 | that.find('.btn-primary').first().click(); |
|
108 | 108 | return false; |
|
109 | 109 | } |
@@ -111,24 +111,24 b' var IPython = (function (IPython) {' | |||
|
111 | 111 | that.find('input[type="text"]').focus().select(); |
|
112 | 112 | } |
|
113 | 113 | }); |
|
114 | } | |
|
114 | }; | |
|
115 | 115 | |
|
116 | 116 | |
|
117 | 117 | SaveWidget.prototype.update_notebook_name = function () { |
|
118 |
var nbname = |
|
|
118 | var nbname = this.notebook.get_notebook_name(); | |
|
119 | 119 | this.element.find('span#notebook_name').text(nbname); |
|
120 | 120 | }; |
|
121 | 121 | |
|
122 | 122 | |
|
123 | 123 | SaveWidget.prototype.update_document_title = function () { |
|
124 |
var nbname = |
|
|
124 | var nbname = this.notebook.get_notebook_name(); | |
|
125 | 125 | document.title = nbname; |
|
126 | 126 | }; |
|
127 | 127 | |
|
128 | 128 | SaveWidget.prototype.update_address_bar = function(){ |
|
129 |
var base_url = |
|
|
130 |
var nbname = |
|
|
131 |
var path = |
|
|
129 | var base_url = this.notebook.base_url; | |
|
130 | var nbname = this.notebook.notebook_name; | |
|
131 | var path = this.notebook.notebook_path; | |
|
132 | 132 | var state = {path : path, name: nbname}; |
|
133 | 133 | window.history.replaceState(state, "", utils.url_join_encode( |
|
134 | 134 | base_url, |
@@ -141,23 +141,91 b' var IPython = (function (IPython) {' | |||
|
141 | 141 | |
|
142 | 142 | SaveWidget.prototype.set_save_status = function (msg) { |
|
143 | 143 | this.element.find('span#autosave_status').text(msg); |
|
144 | }; | |
|
145 | ||
|
146 | SaveWidget.prototype._set_checkpoint_status = function (human_date, iso_date) { | |
|
147 | var el = this.element.find('span#checkpoint_status') | |
|
148 | if(human_date){ | |
|
149 | el.text("Last Checkpoint: "+human_date).attr('title',iso_date); | |
|
150 | } else { | |
|
151 | el.text('').attr('title','no-checkpoint') | |
|
144 | 152 | } |
|
153 | }; | |
|
154 | ||
|
155 | // compute (roughly) the remaining time in millisecond until the next | |
|
156 | // moment.js relative time update of the string, which by default | |
|
157 | // happend at | |
|
158 | // (a few seconds ago) | |
|
159 | // - 45sec, | |
|
160 | // (a minute ago) | |
|
161 | // - 90sec, | |
|
162 | // ( x minutes ago) | |
|
163 | // - then every minutes until | |
|
164 | // - 45 min, | |
|
165 | // (an hour ago) | |
|
166 | // - 1h45, | |
|
167 | // (x hours ago ) | |
|
168 | // - then every hours | |
|
169 | // - 22 hours ago | |
|
170 | var _next_timeago_update = function(deltatime_ms){ | |
|
171 | var s = 1000; | |
|
172 | var m = 60*s; | |
|
173 | var h = 60*m; | |
|
174 | ||
|
175 | var mtt = moment.relativeTimeThreshold; | |
|
176 | ||
|
177 | if(deltatime_ms < mtt.s*s){ | |
|
178 | return mtt.s*s-deltatime_ms; | |
|
179 | } else if (deltatime_ms < (mtt.s*s+m)) { | |
|
180 | return (mtt.s*s+m)-deltatime_ms; | |
|
181 | } else if (deltatime_ms < mtt.m*m){ | |
|
182 | return m; | |
|
183 | } else if (deltatime_ms < (mtt.m*m+h)){ | |
|
184 | return (mtt.m*m+h)-deltatime_ms; | |
|
185 | } else { | |
|
186 | return h; | |
|
187 | } | |
|
188 | ||
|
145 | 189 | |
|
146 | SaveWidget.prototype.set_checkpoint_status = function (msg) { | |
|
147 | this.element.find('span#checkpoint_status').text(msg); | |
|
148 | 190 | } |
|
149 | 191 | |
|
150 |
SaveWidget.prototype. |
|
|
151 |
|
|
|
152 |
this.set_checkpoint_status( |
|
|
192 | SaveWidget.prototype._regularly_update_checkpoint_date = function(){ | |
|
193 | if (!this._checkpoint_date) { | |
|
194 | this.set_checkpoint_status(null); | |
|
195 | console.log('no checkpoint done'); | |
|
153 | 196 | return; |
|
154 | 197 | } |
|
155 |
var d = |
|
|
156 | this.set_checkpoint_status( | |
|
157 | "Last Checkpoint: " + d.format('mmm dd HH:MM') | |
|
158 | ); | |
|
198 | var chkd = moment(this._checkpoint_date); | |
|
199 | var longdate = chkd.format('llll'); | |
|
200 | ||
|
201 | var that = this; | |
|
202 | var recall = function(t){ | |
|
203 | // recall slightly later (1s) as long timeout in js might be imprecise, | |
|
204 | // and you want to be call **after** the change of formatting should happend. | |
|
205 | return setTimeout($.proxy(that._regularly_update_checkpoint_date, that),(t+1000)) | |
|
206 | } | |
|
207 | var tdelta = Math.ceil(new Date()-this._checkpoint_date); | |
|
208 | ||
|
209 | // update regularly for the first 6hours and show | |
|
210 | // <x time> ago | |
|
211 | if(tdelta < tdelta < 6*3600*1000){ | |
|
212 | recall(_next_timeago_update(tdelta)); | |
|
213 | this._set_checkpoint_status( chkd.fromNow(), longdate); | |
|
214 | // otherwise update every hour and show | |
|
215 | // <Today | yesterday|...> at hh,mm,ss | |
|
216 | } else { | |
|
217 | recall(1*3600*1000) | |
|
218 | this._set_checkpoint_status( chkd.calendar(), longdate); | |
|
219 | } | |
|
220 | ||
|
159 | 221 | } |
|
160 | 222 | |
|
223 | SaveWidget.prototype._set_last_checkpoint = function (checkpoint) { | |
|
224 | this._checkpoint_date = new Date(checkpoint.last_modified); | |
|
225 | this._regularly_update_checkpoint_date(); | |
|
226 | ||
|
227 | }; | |
|
228 | ||
|
161 | 229 | SaveWidget.prototype.set_autosaved = function (dirty) { |
|
162 | 230 | if (dirty) { |
|
163 | 231 | this.set_save_status("(unsaved changes)"); |
@@ -166,10 +234,9 b' var IPython = (function (IPython) {' | |||
|
166 | 234 | } |
|
167 | 235 | }; |
|
168 | 236 | |
|
169 | ||
|
237 | // Backwards compatibility. | |
|
170 | 238 | IPython.SaveWidget = SaveWidget; |
|
171 | 239 | |
|
172 | return IPython; | |
|
173 | ||
|
174 | }(IPython)); | |
|
240 | return {'SaveWidget': SaveWidget}; | |
|
175 | 241 | |
|
242 | }); |
@@ -1,60 +1,57 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008-2012 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
7 | ||
|
8 | //============================================================================ | |
|
9 | // TextCell | |
|
10 | //============================================================================ | |
|
11 | ||
|
12 | ||
|
13 | ||
|
14 | /** | |
|
15 | A module that allow to create different type of Text Cell | |
|
16 | @module IPython | |
|
17 | @namespace IPython | |
|
18 | */ | |
|
19 | var IPython = (function (IPython) { | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
3 | ||
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'notebook/js/cell', | |
|
8 | 'base/js/security', | |
|
9 | 'notebook/js/mathjaxutils', | |
|
10 | 'notebook/js/celltoolbar', | |
|
11 | 'components/marked/lib/marked', | |
|
12 | ], function(IPython, $, cell, security, mathjaxutils, celltoolbar, marked) { | |
|
20 | 13 | "use strict"; |
|
14 | var Cell = cell.Cell; | |
|
21 | 15 | |
|
22 | // TextCell base class | |
|
23 | var keycodes = IPython.keyboard.keycodes; | |
|
24 | var security = IPython.security; | |
|
25 | ||
|
26 | /** | |
|
27 | * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text' | |
|
28 | * cell start as not redered. | |
|
29 | * | |
|
30 | * @class TextCell | |
|
31 | * @constructor TextCell | |
|
32 | * @extend IPython.Cell | |
|
33 | * @param {object|undefined} [options] | |
|
34 | * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config | |
|
35 | * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass) | |
|
36 | */ | |
|
37 | 16 | var TextCell = function (options) { |
|
17 | // Constructor | |
|
18 | // | |
|
19 | // Construct a new TextCell, codemirror mode is by default 'htmlmixed', | |
|
20 | // and cell type is 'text' cell start as not redered. | |
|
21 | // | |
|
22 | // Parameters: | |
|
23 | // options: dictionary | |
|
24 | // Dictionary of keyword arguments. | |
|
25 | // events: $(Events) instance | |
|
26 | // config: dictionary | |
|
27 | // keyboard_manager: KeyboardManager instance | |
|
28 | // notebook: Notebook instance | |
|
29 | options = options || {}; | |
|
30 | ||
|
38 | 31 | // in all TextCell/Cell subclasses |
|
39 | 32 | // do not assign most of members here, just pass it down |
|
40 | 33 | // in the options dict potentially overwriting what you wish. |
|
41 | 34 | // they will be assigned in the base class. |
|
35 | this.notebook = options.notebook; | |
|
36 | this.events = options.events; | |
|
37 | this.config = options.config; | |
|
42 | 38 | |
|
43 | 39 | // we cannot put this as a class key as it has handle to "this". |
|
44 | 40 | var cm_overwrite_options = { |
|
45 | 41 | onKeyEvent: $.proxy(this.handle_keyevent,this) |
|
46 | 42 | }; |
|
47 | ||
|
48 | options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options}); | |
|
43 | var config = this.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options}); | |
|
44 | Cell.apply(this, [{ | |
|
45 | config: config, | |
|
46 | keyboard_manager: options.keyboard_manager, | |
|
47 | events: this.events}]); | |
|
49 | 48 | |
|
50 | 49 | this.cell_type = this.cell_type || 'text'; |
|
51 | ||
|
52 | IPython.Cell.apply(this, [options]); | |
|
53 | ||
|
50 | mathjaxutils = mathjaxutils; | |
|
54 | 51 | this.rendered = false; |
|
55 | 52 | }; |
|
56 | 53 | |
|
57 |
TextCell.prototype = new |
|
|
54 | TextCell.prototype = new Cell(); | |
|
58 | 55 | |
|
59 | 56 | TextCell.options_default = { |
|
60 | 57 | cm_config : { |
@@ -71,21 +68,23 b' var IPython = (function (IPython) {' | |||
|
71 | 68 | * @private |
|
72 | 69 | */ |
|
73 | 70 | TextCell.prototype.create_element = function () { |
|
74 |
|
|
|
71 | Cell.prototype.create_element.apply(this, arguments); | |
|
75 | 72 | |
|
76 |
var cell = $("<div>").addClass('cell text_cell |
|
|
73 | var cell = $("<div>").addClass('cell text_cell'); | |
|
77 | 74 | cell.attr('tabindex','2'); |
|
78 | 75 | |
|
79 | 76 | var prompt = $('<div/>').addClass('prompt input_prompt'); |
|
80 | 77 | cell.append(prompt); |
|
81 | 78 | var inner_cell = $('<div/>').addClass('inner_cell'); |
|
82 |
this.celltoolbar = new |
|
|
79 | this.celltoolbar = new celltoolbar.CellToolbar({ | |
|
80 | cell: this, | |
|
81 | notebook: this.notebook}); | |
|
83 | 82 | inner_cell.append(this.celltoolbar.element); |
|
84 | 83 | var input_area = $('<div/>').addClass('input_area'); |
|
85 | 84 | this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config); |
|
86 | 85 | // The tabindex=-1 makes this div focusable. |
|
87 |
var render_area = $('<div/>').addClass('text_cell_render |
|
|
88 |
|
|
|
86 | var render_area = $('<div/>').addClass('text_cell_render rendered_html') | |
|
87 | .attr('tabindex','-1'); | |
|
89 | 88 | inner_cell.append(input_area).append(render_area); |
|
90 | 89 | cell.append(inner_cell); |
|
91 | 90 | this.element = cell; |
@@ -99,12 +98,12 b' var IPython = (function (IPython) {' | |||
|
99 | 98 | * @method bind_event |
|
100 | 99 | */ |
|
101 | 100 | TextCell.prototype.bind_events = function () { |
|
102 |
|
|
|
101 | Cell.prototype.bind_events.apply(this); | |
|
103 | 102 | var that = this; |
|
104 | 103 | |
|
105 | 104 | this.element.dblclick(function () { |
|
106 | 105 | if (that.selected === false) { |
|
107 |
|
|
|
106 | this.events.trigger('select.Cell', {'cell':that}); | |
|
108 | 107 | } |
|
109 | 108 | var cont = that.unrender(); |
|
110 | 109 | if (cont) { |
@@ -116,7 +115,7 b' var IPython = (function (IPython) {' | |||
|
116 | 115 | // Cell level actions |
|
117 | 116 | |
|
118 | 117 | TextCell.prototype.select = function () { |
|
119 |
var cont = |
|
|
118 | var cont = Cell.prototype.select.apply(this); | |
|
120 | 119 | if (cont) { |
|
121 | 120 | if (this.mode === 'edit') { |
|
122 | 121 | this.code_mirror.refresh(); |
@@ -127,7 +126,7 b' var IPython = (function (IPython) {' | |||
|
127 | 126 | |
|
128 | 127 | TextCell.prototype.unrender = function () { |
|
129 | 128 | if (this.read_only) return; |
|
130 |
var cont = |
|
|
129 | var cont = Cell.prototype.unrender.apply(this); | |
|
131 | 130 | if (cont) { |
|
132 | 131 | var text_cell = this.element; |
|
133 | 132 | var output = text_cell.find("div.text_cell_render"); |
@@ -164,13 +163,13 b' var IPython = (function (IPython) {' | |||
|
164 | 163 | * */ |
|
165 | 164 | TextCell.prototype.set_text = function(text) { |
|
166 | 165 | this.code_mirror.setValue(text); |
|
166 | this.unrender(); | |
|
167 | 167 | this.code_mirror.refresh(); |
|
168 | 168 | }; |
|
169 | 169 | |
|
170 | 170 | /** |
|
171 | 171 | * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}} |
|
172 | 172 | * @method get_rendered |
|
173 | * @return {html} html of rendered element | |
|
174 | 173 | * */ |
|
175 | 174 | TextCell.prototype.get_rendered = function() { |
|
176 | 175 | return this.element.find('div.text_cell_render').html(); |
@@ -191,7 +190,7 b' var IPython = (function (IPython) {' | |||
|
191 | 190 | * @method fromJSON |
|
192 | 191 | */ |
|
193 | 192 | TextCell.prototype.fromJSON = function (data) { |
|
194 |
|
|
|
193 | Cell.prototype.fromJSON.apply(this, arguments); | |
|
195 | 194 | if (data.cell_type === this.cell_type) { |
|
196 | 195 | if (data.source !== undefined) { |
|
197 | 196 | this.set_text(data.source); |
@@ -211,7 +210,7 b' var IPython = (function (IPython) {' | |||
|
211 | 210 | * @return {object} cell data serialised to json |
|
212 | 211 | */ |
|
213 | 212 | TextCell.prototype.toJSON = function () { |
|
214 |
var data = |
|
|
213 | var data = Cell.prototype.toJSON.apply(this); | |
|
215 | 214 | data.source = this.get_text(); |
|
216 | 215 | if (data.source == this.placeholder) { |
|
217 | 216 | data.source = ""; |
@@ -220,16 +219,21 b' var IPython = (function (IPython) {' | |||
|
220 | 219 | }; |
|
221 | 220 | |
|
222 | 221 | |
|
223 | /** | |
|
224 | * @class MarkdownCell | |
|
225 | * @constructor MarkdownCell | |
|
226 | * @extends IPython.HTMLCell | |
|
227 | */ | |
|
228 | 222 | var MarkdownCell = function (options) { |
|
229 | options = this.mergeopt(MarkdownCell, options); | |
|
223 | // Constructor | |
|
224 | // | |
|
225 | // Parameters: | |
|
226 | // options: dictionary | |
|
227 | // Dictionary of keyword arguments. | |
|
228 | // events: $(Events) instance | |
|
229 | // config: dictionary | |
|
230 | // keyboard_manager: KeyboardManager instance | |
|
231 | // notebook: Notebook instance | |
|
232 | options = options || {}; | |
|
233 | var config = this.mergeopt(MarkdownCell, options.config); | |
|
234 | TextCell.apply(this, [$.extend({}, options, {config: config})]); | |
|
230 | 235 | |
|
231 | 236 | this.cell_type = 'markdown'; |
|
232 | TextCell.apply(this, [options]); | |
|
233 | 237 | }; |
|
234 | 238 | |
|
235 | 239 | MarkdownCell.options_default = { |
@@ -245,16 +249,16 b' var IPython = (function (IPython) {' | |||
|
245 | 249 | * @method render |
|
246 | 250 | */ |
|
247 | 251 | MarkdownCell.prototype.render = function () { |
|
248 |
var cont = |
|
|
252 | var cont = TextCell.prototype.render.apply(this); | |
|
249 | 253 | if (cont) { |
|
250 | 254 | var text = this.get_text(); |
|
251 | 255 | var math = null; |
|
252 | 256 | if (text === "") { text = this.placeholder; } |
|
253 |
var text_and_math = |
|
|
257 | var text_and_math = mathjaxutils.remove_math(text); | |
|
254 | 258 | text = text_and_math[0]; |
|
255 | 259 | math = text_and_math[1]; |
|
256 | 260 | var html = marked.parser(marked.lexer(text)); |
|
257 |
html = |
|
|
261 | html = mathjaxutils.replace_math(html, math); | |
|
258 | 262 | html = security.sanitize_html(html); |
|
259 | 263 | html = $($.parseHTML(html)); |
|
260 | 264 | // links in markdown cells should open in new tabs |
@@ -268,20 +272,23 b' var IPython = (function (IPython) {' | |||
|
268 | 272 | }; |
|
269 | 273 | |
|
270 | 274 | |
|
271 | // RawCell | |
|
272 | ||
|
273 | /** | |
|
274 | * @class RawCell | |
|
275 | * @constructor RawCell | |
|
276 | * @extends IPython.TextCell | |
|
277 | */ | |
|
278 | 275 | var RawCell = function (options) { |
|
276 | // Constructor | |
|
277 | // | |
|
278 | // Parameters: | |
|
279 | // options: dictionary | |
|
280 | // Dictionary of keyword arguments. | |
|
281 | // events: $(Events) instance | |
|
282 | // config: dictionary | |
|
283 | // keyboard_manager: KeyboardManager instance | |
|
284 | // notebook: Notebook instance | |
|
285 | options = options || {}; | |
|
286 | var config = this.mergeopt(RawCell, options.config); | |
|
287 | TextCell.apply(this, [$.extend({}, options, {config: config})]); | |
|
279 | 288 | |
|
280 | options = this.mergeopt(RawCell,options); | |
|
281 | TextCell.apply(this, [options]); | |
|
282 | this.cell_type = 'raw'; | |
|
283 | 289 | // RawCell should always hide its rendered div |
|
284 | 290 | this.element.find('div.text_cell_render').hide(); |
|
291 | this.cell_type = 'raw'; | |
|
285 | 292 | }; |
|
286 | 293 | |
|
287 | 294 | RawCell.options_default = { |
@@ -309,12 +316,12 b' var IPython = (function (IPython) {' | |||
|
309 | 316 | * @method auto_highlight |
|
310 | 317 | */ |
|
311 | 318 | RawCell.prototype.auto_highlight = function () { |
|
312 |
this._auto_highlight( |
|
|
319 | this._auto_highlight(this.config.raw_cell_highlight); | |
|
313 | 320 | }; |
|
314 | 321 | |
|
315 | 322 | /** @method render **/ |
|
316 | 323 | RawCell.prototype.render = function () { |
|
317 |
var cont = |
|
|
324 | var cont = TextCell.prototype.render.apply(this); | |
|
318 | 325 | if (cont){ |
|
319 | 326 | var text = this.get_text(); |
|
320 | 327 | if (text === "") { text = this.placeholder; } |
@@ -325,29 +332,28 b' var IPython = (function (IPython) {' | |||
|
325 | 332 | }; |
|
326 | 333 | |
|
327 | 334 | |
|
328 | /** | |
|
329 | * @class HeadingCell | |
|
330 | * @extends IPython.TextCell | |
|
331 | */ | |
|
332 | ||
|
333 | /** | |
|
334 | * @constructor HeadingCell | |
|
335 | * @extends IPython.TextCell | |
|
336 | */ | |
|
337 | 335 | var HeadingCell = function (options) { |
|
338 | options = this.mergeopt(HeadingCell, options); | |
|
336 | // Constructor | |
|
337 | // | |
|
338 | // Parameters: | |
|
339 | // options: dictionary | |
|
340 | // Dictionary of keyword arguments. | |
|
341 | // events: $(Events) instance | |
|
342 | // config: dictionary | |
|
343 | // keyboard_manager: KeyboardManager instance | |
|
344 | // notebook: Notebook instance | |
|
345 | options = options || {}; | |
|
346 | var config = this.mergeopt(HeadingCell, options.config); | |
|
347 | TextCell.apply(this, [$.extend({}, options, {config: config})]); | |
|
339 | 348 | |
|
340 | 349 | this.level = 1; |
|
341 | 350 | this.cell_type = 'heading'; |
|
342 | TextCell.apply(this, [options]); | |
|
343 | ||
|
344 | /** | |
|
345 | * heading level of the cell, use getter and setter to access | |
|
346 | * @property level | |
|
347 | */ | |
|
348 | 351 | }; |
|
349 | 352 | |
|
350 | 353 | HeadingCell.options_default = { |
|
354 | cm_config: { | |
|
355 | theme: 'heading-1' | |
|
356 | }, | |
|
351 | 357 | placeholder: "Type Heading Here" |
|
352 | 358 | }; |
|
353 | 359 | |
@@ -359,6 +365,7 b' var IPython = (function (IPython) {' | |||
|
359 | 365 | this.level = data.level; |
|
360 | 366 | } |
|
361 | 367 | TextCell.prototype.fromJSON.apply(this, arguments); |
|
368 | this.code_mirror.setOption("theme", "heading-"+this.level); | |
|
362 | 369 | }; |
|
363 | 370 | |
|
364 | 371 | |
@@ -370,28 +377,13 b' var IPython = (function (IPython) {' | |||
|
370 | 377 | }; |
|
371 | 378 | |
|
372 | 379 | /** |
|
373 | * can the cell be split into two cells | |
|
374 | * @method is_splittable | |
|
375 | **/ | |
|
376 | HeadingCell.prototype.is_splittable = function () { | |
|
377 | return false; | |
|
378 | }; | |
|
379 | ||
|
380 | ||
|
381 | /** | |
|
382 | * can the cell be merged with other cells | |
|
383 | * @method is_mergeable | |
|
384 | **/ | |
|
385 | HeadingCell.prototype.is_mergeable = function () { | |
|
386 | return false; | |
|
387 | }; | |
|
388 | ||
|
389 | /** | |
|
390 | 380 | * Change heading level of cell, and re-render |
|
391 | 381 | * @method set_level |
|
392 | 382 | */ |
|
393 | 383 | HeadingCell.prototype.set_level = function (level) { |
|
394 | 384 | this.level = level; |
|
385 | this.code_mirror.setOption("theme", "heading-"+level); | |
|
386 | ||
|
395 | 387 | if (this.rendered) { |
|
396 | 388 | this.rendered = false; |
|
397 | 389 | this.render(); |
@@ -412,21 +404,20 b' var IPython = (function (IPython) {' | |||
|
412 | 404 | return r.children().first().html(); |
|
413 | 405 | }; |
|
414 | 406 | |
|
415 | ||
|
416 | 407 | HeadingCell.prototype.render = function () { |
|
417 |
var cont = |
|
|
408 | var cont = TextCell.prototype.render.apply(this); | |
|
418 | 409 | if (cont) { |
|
419 | 410 | var text = this.get_text(); |
|
420 | 411 | var math = null; |
|
421 | 412 | // Markdown headings must be a single line |
|
422 | 413 | text = text.replace(/\n/g, ' '); |
|
423 | 414 | if (text === "") { text = this.placeholder; } |
|
424 | text = Array(this.level + 1).join("#") + " " + text; | |
|
425 |
var text_and_math = |
|
|
415 | text = new Array(this.level + 1).join("#") + " " + text; | |
|
416 | var text_and_math = mathjaxutils.remove_math(text); | |
|
426 | 417 | text = text_and_math[0]; |
|
427 | 418 | math = text_and_math[1]; |
|
428 | 419 | var html = marked.parser(marked.lexer(text)); |
|
429 |
html = |
|
|
420 | html = mathjaxutils.replace_math(html, math); | |
|
430 | 421 | html = security.sanitize_html(html); |
|
431 | 422 | var h = $($.parseHTML(html)); |
|
432 | 423 | // add id and linkback anchor |
@@ -446,13 +437,17 b' var IPython = (function (IPython) {' | |||
|
446 | 437 | return cont; |
|
447 | 438 | }; |
|
448 | 439 | |
|
440 | // Backwards compatability. | |
|
449 | 441 | IPython.TextCell = TextCell; |
|
450 | 442 | IPython.MarkdownCell = MarkdownCell; |
|
451 | 443 | IPython.RawCell = RawCell; |
|
452 | 444 | IPython.HeadingCell = HeadingCell; |
|
453 | 445 | |
|
454 | ||
|
455 | return IPython; | |
|
456 | ||
|
457 | }(IPython)); | |
|
458 | ||
|
446 | var textcell = { | |
|
447 | 'TextCell': TextCell, | |
|
448 | 'MarkdownCell': MarkdownCell, | |
|
449 | 'RawCell': RawCell, | |
|
450 | 'HeadingCell': HeadingCell, | |
|
451 | }; | |
|
452 | return textcell; | |
|
453 | }); |
@@ -1,20 +1,10 b'' | |||
|
1 | //---------------------------------------------------------------------------- | |
|
2 | // Copyright (C) 2008 The IPython Development Team | |
|
3 | // | |
|
4 | // Distributed under the terms of the BSD License. The full license is in | |
|
5 | // the file COPYING, distributed as part of this software. | |
|
6 | //---------------------------------------------------------------------------- | |
|
1 | // Copyright (c) IPython Development Team. | |
|
2 | // Distributed under the terms of the Modified BSD License. | |
|
7 | 3 | |
|
8 | //============================================================================ | |
|
9 | // ToolBar | |
|
10 | //============================================================================ | |
|
11 | /** | |
|
12 | * @module IPython | |
|
13 | * @namespace IPython | |
|
14 | * @submodule ToolBar | |
|
15 | */ | |
|
16 | ||
|
17 | var IPython = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | ], function(IPython, $) { | |
|
18 | 8 | "use strict"; |
|
19 | 9 | |
|
20 | 10 | /** |
@@ -23,8 +13,9 b' var IPython = (function (IPython) {' | |||
|
23 | 13 | * @constructor |
|
24 | 14 | * @param {Dom object} selector |
|
25 | 15 | */ |
|
26 | var ToolBar = function (selector) { | |
|
16 | var ToolBar = function (selector, layout_manager) { | |
|
27 | 17 | this.selector = selector; |
|
18 | this.layout_manager = layout_manager; | |
|
28 | 19 | if (this.selector !== undefined) { |
|
29 | 20 | this.element = $(selector); |
|
30 | 21 | this.style(); |
@@ -75,7 +66,7 b' var IPython = (function (IPython) {' | |||
|
75 | 66 | .addClass('btn btn-default') |
|
76 | 67 | .attr("title", el.label) |
|
77 | 68 | .append( |
|
78 | $("<i/>").addClass(el.icon) | |
|
69 | $("<i/>").addClass(el.icon).addClass('fa') | |
|
79 | 70 | ); |
|
80 | 71 | var id = el.id; |
|
81 | 72 | if( id !== undefined ) |
@@ -88,8 +79,7 b' var IPython = (function (IPython) {' | |||
|
88 | 79 | }; |
|
89 | 80 | |
|
90 | 81 | ToolBar.prototype.style = function () { |
|
91 |
this.element.addClass(' |
|
|
92 | .addClass('toolbar'); | |
|
82 | this.element.addClass('toolbar'); | |
|
93 | 83 | }; |
|
94 | 84 | |
|
95 | 85 | /** |
@@ -98,14 +88,13 b' var IPython = (function (IPython) {' | |||
|
98 | 88 | */ |
|
99 | 89 | ToolBar.prototype.toggle = function () { |
|
100 | 90 | this.element.toggle(); |
|
101 |
if ( |
|
|
102 |
|
|
|
91 | if (this.layout_manager !== undefined) { | |
|
92 | this.layout_manager.do_resize(); | |
|
103 | 93 | } |
|
104 | 94 | }; |
|
105 | 95 | |
|
106 | ||
|
96 | // Backwards compatibility. | |
|
107 | 97 | IPython.ToolBar = ToolBar; |
|
108 | 98 | |
|
109 | return IPython; | |
|
110 | ||
|
111 | }(IPython)); | |
|
99 | return {'ToolBar': ToolBar}; | |
|
100 | }); |
@@ -1,27 +1,17 b'' | |||
|
1 | 1 | // Copyright (c) IPython Development Team. |
|
2 | 2 | // Distributed under the terms of the Modified BSD License. |
|
3 | 3 | |
|
4 | //============================================================================ | |
|
5 | // Tooltip | |
|
6 | //============================================================================ | |
|
7 | // | |
|
8 | // you can set the autocall time by setting `IPython.tooltip.time_before_tooltip` in ms | |
|
9 | // | |
|
10 | // you can configure the differents action of pressing shift-tab several times in a row by | |
|
11 | // setting/appending different fonction in the array | |
|
12 | // IPython.tooltip.tabs_functions | |
|
13 | // | |
|
14 | // eg : | |
|
15 | // IPython.tooltip.tabs_functions[4] = function (){console.log('this is the action of the 4th tab pressing')} | |
|
16 | // | |
|
17 | var IPython = (function (IPython) { | |
|
4 | define([ | |
|
5 | 'base/js/namespace', | |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |
|
8 | ], function(IPython, $, utils) { | |
|
18 | 9 | "use strict"; |
|
19 | 10 | |
|
20 | var utils = IPython.utils; | |
|
21 | ||
|
22 | 11 | // tooltip constructor |
|
23 | var Tooltip = function () { | |
|
12 | var Tooltip = function (events) { | |
|
24 | 13 |
|
|
14 | this.events = events; | |
|
25 | 15 |
|
|
26 | 16 | |
|
27 | 17 |
|
@@ -131,7 +121,7 b' var IPython = (function (IPython) {' | |||
|
131 | 121 | var payload = {}; |
|
132 | 122 | payload.text = that._reply.content.data['text/plain']; |
|
133 | 123 | |
|
134 |
|
|
|
124 | this.events.trigger('open_with_text.Pager', payload); | |
|
135 | 125 | this.remove_and_cancel_tooltip(); |
|
136 | 126 | }; |
|
137 | 127 | |
@@ -338,8 +328,8 b' var IPython = (function (IPython) {' | |||
|
338 | 328 | this.text.scrollTop(0); |
|
339 | 329 | }; |
|
340 | 330 | |
|
331 | // Backwards compatability. | |
|
341 | 332 | IPython.Tooltip = Tooltip; |
|
342 | 333 | |
|
343 | return IPython; | |
|
344 | ||
|
345 | }(IPython)); | |
|
334 | return {'Tooltip': Tooltip}; | |
|
335 | }); |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file renamed from IPython/html/static/widgets/js/widget_container.js to IPython/html/static/widgets/js/widget_box.js | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file renamed from IPython/html/tests/widgets/widget_container.js to IPython/html/tests/widgets/widget_box.js | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file chmod 100755 => 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file renamed from examples/Notebook/Animations Using clear_output.ipynb to examples/IPython Kernel/Animations Using clear_output.ipynb |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file renamed from examples/Notebook/Plotting with Matplotlib.ipynb to examples/IPython Kernel/Plotting in the Notebook.ipynb |
|
1 | NO CONTENT: file renamed from examples/Notebook/Raw Input.ipynb to examples/IPython Kernel/Raw Input in the Notebook.ipynb | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file renamed from examples/Notebook/Display System.ipynb to examples/IPython Kernel/Rich Output.ipynb | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file renamed from examples/Notebook/SymPy.ipynb to examples/IPython Kernel/SymPy.ipynb |
|
1 | NO CONTENT: file renamed from examples/Interactive Widgets/Widget Styles.ipynb to examples/IPython Kernel/Terminal Usage.ipynb | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file renamed from examples/Notebook/Trapezoid Rule.ipynb to examples/IPython Kernel/Trapezoid Rule.ipynb |
|
1 | NO CONTENT: file renamed from examples/Interactive Widgets/Custom Widgets.ipynb to examples/Interactive Widgets/Date Picker Widget.ipynb |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file renamed from examples/Notebook/User Interface.ipynb to examples/Notebook/Running the Notebook Server.ipynb | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file renamed from examples/Notebook/Typesetting Math Using MathJax.ipynb to examples/Notebook/Typesetting Equations.ipynb |
|
1 | NO CONTENT: file renamed from examples/Notebook/Markdown Cells.ipynb to examples/Notebook/Working With Markdown Cells.ipynb | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file renamed from examples/Notebook/images/animation.m4v to examples/images/animation.m4v |
|
1 | NO CONTENT: file renamed from examples/Notebook/images/python_logo.svg to examples/images/python_logo.svg |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
General Comments 0
You need to be logged in to leave comments.
Login now