Show More
The requested changes are too big and content was truncated. Show full diff
This diff has been collapsed as it changes many lines, (531 lines changed) Show them Hide them | |||||
@@ -0,0 +1,531 b'' | |||||
|
1 | """A contents manager that uses the local file system for storage.""" | |||
|
2 | ||||
|
3 | # Copyright (c) IPython Development Team. | |||
|
4 | # Distributed under the terms of the Modified BSD License. | |||
|
5 | ||||
|
6 | import base64 | |||
|
7 | import io | |||
|
8 | import os | |||
|
9 | import glob | |||
|
10 | import shutil | |||
|
11 | ||||
|
12 | from tornado import web | |||
|
13 | ||||
|
14 | from .manager import ContentsManager | |||
|
15 | from IPython.nbformat import current | |||
|
16 | from IPython.utils.path import ensure_dir_exists | |||
|
17 | from IPython.utils.traitlets import Unicode, Bool, TraitError | |||
|
18 | from IPython.utils.py3compat import getcwd | |||
|
19 | from IPython.utils import tz | |||
|
20 | from IPython.html.utils import is_hidden, to_os_path, url_path_join | |||
|
21 | ||||
|
22 | ||||
|
23 | class FileContentsManager(ContentsManager): | |||
|
24 | ||||
|
25 | root_dir = Unicode(getcwd(), config=True) | |||
|
26 | ||||
|
27 | save_script = Bool(False, config=True, help='DEPRECATED, IGNORED') | |||
|
28 | def _save_script_changed(self): | |||
|
29 | self.log.warn(""" | |||
|
30 | Automatically saving notebooks as scripts has been removed. | |||
|
31 | Use `ipython nbconvert --to python [notebook]` instead. | |||
|
32 | """) | |||
|
33 | ||||
|
34 | def _root_dir_changed(self, name, old, new): | |||
|
35 | """Do a bit of validation of the root_dir.""" | |||
|
36 | if not os.path.isabs(new): | |||
|
37 | # If we receive a non-absolute path, make it absolute. | |||
|
38 | self.root_dir = os.path.abspath(new) | |||
|
39 | return | |||
|
40 | if not os.path.isdir(new): | |||
|
41 | raise TraitError("%r is not a directory" % new) | |||
|
42 | ||||
|
43 | checkpoint_dir = Unicode('.ipynb_checkpoints', config=True, | |||
|
44 | help="""The directory name in which to keep file checkpoints | |||
|
45 | ||||
|
46 | This is a path relative to the file's own directory. | |||
|
47 | ||||
|
48 | By default, it is .ipynb_checkpoints | |||
|
49 | """ | |||
|
50 | ) | |||
|
51 | ||||
|
52 | def _copy(self, src, dest): | |||
|
53 | """copy src to dest | |||
|
54 | ||||
|
55 | like shutil.copy2, but log errors in copystat | |||
|
56 | """ | |||
|
57 | shutil.copyfile(src, dest) | |||
|
58 | try: | |||
|
59 | shutil.copystat(src, dest) | |||
|
60 | except OSError as e: | |||
|
61 | self.log.debug("copystat on %s failed", dest, exc_info=True) | |||
|
62 | ||||
|
63 | def _get_os_path(self, name=None, path=''): | |||
|
64 | """Given a filename and API path, return its file system | |||
|
65 | path. | |||
|
66 | ||||
|
67 | Parameters | |||
|
68 | ---------- | |||
|
69 | name : string | |||
|
70 | A filename | |||
|
71 | path : string | |||
|
72 | The relative API path to the named file. | |||
|
73 | ||||
|
74 | Returns | |||
|
75 | ------- | |||
|
76 | path : string | |||
|
77 | API path to be evaluated relative to root_dir. | |||
|
78 | """ | |||
|
79 | if name is not None: | |||
|
80 | path = url_path_join(path, name) | |||
|
81 | return to_os_path(path, self.root_dir) | |||
|
82 | ||||
|
83 | def path_exists(self, path): | |||
|
84 | """Does the API-style path refer to an extant directory? | |||
|
85 | ||||
|
86 | API-style wrapper for os.path.isdir | |||
|
87 | ||||
|
88 | Parameters | |||
|
89 | ---------- | |||
|
90 | path : string | |||
|
91 | The path to check. This is an API path (`/` separated, | |||
|
92 | relative to root_dir). | |||
|
93 | ||||
|
94 | Returns | |||
|
95 | ------- | |||
|
96 | exists : bool | |||
|
97 | Whether the path is indeed a directory. | |||
|
98 | """ | |||
|
99 | path = path.strip('/') | |||
|
100 | os_path = self._get_os_path(path=path) | |||
|
101 | return os.path.isdir(os_path) | |||
|
102 | ||||
|
103 | def is_hidden(self, path): | |||
|
104 | """Does the API style path correspond to a hidden directory or file? | |||
|
105 | ||||
|
106 | Parameters | |||
|
107 | ---------- | |||
|
108 | path : string | |||
|
109 | The path to check. This is an API path (`/` separated, | |||
|
110 | relative to root_dir). | |||
|
111 | ||||
|
112 | Returns | |||
|
113 | ------- | |||
|
114 | exists : bool | |||
|
115 | Whether the path is hidden. | |||
|
116 | ||||
|
117 | """ | |||
|
118 | path = path.strip('/') | |||
|
119 | os_path = self._get_os_path(path=path) | |||
|
120 | return is_hidden(os_path, self.root_dir) | |||
|
121 | ||||
|
122 | def file_exists(self, name, path=''): | |||
|
123 | """Returns True if the file exists, else returns False. | |||
|
124 | ||||
|
125 | API-style wrapper for os.path.isfile | |||
|
126 | ||||
|
127 | Parameters | |||
|
128 | ---------- | |||
|
129 | name : string | |||
|
130 | The name of the file you are checking. | |||
|
131 | path : string | |||
|
132 | The relative path to the file's directory (with '/' as separator) | |||
|
133 | ||||
|
134 | Returns | |||
|
135 | ------- | |||
|
136 | exists : bool | |||
|
137 | Whether the file exists. | |||
|
138 | """ | |||
|
139 | path = path.strip('/') | |||
|
140 | nbpath = self._get_os_path(name, path=path) | |||
|
141 | return os.path.isfile(nbpath) | |||
|
142 | ||||
|
143 | def exists(self, name=None, path=''): | |||
|
144 | """Returns True if the path [and name] exists, else returns False. | |||
|
145 | ||||
|
146 | API-style wrapper for os.path.exists | |||
|
147 | ||||
|
148 | Parameters | |||
|
149 | ---------- | |||
|
150 | name : string | |||
|
151 | The name of the file you are checking. | |||
|
152 | path : string | |||
|
153 | The relative path to the file's directory (with '/' as separator) | |||
|
154 | ||||
|
155 | Returns | |||
|
156 | ------- | |||
|
157 | exists : bool | |||
|
158 | Whether the target exists. | |||
|
159 | """ | |||
|
160 | path = path.strip('/') | |||
|
161 | os_path = self._get_os_path(name, path=path) | |||
|
162 | return os.path.exists(os_path) | |||
|
163 | ||||
|
164 | def _base_model(self, name, path=''): | |||
|
165 | """Build the common base of a contents model""" | |||
|
166 | os_path = self._get_os_path(name, path) | |||
|
167 | info = os.stat(os_path) | |||
|
168 | last_modified = tz.utcfromtimestamp(info.st_mtime) | |||
|
169 | created = tz.utcfromtimestamp(info.st_ctime) | |||
|
170 | # Create the base model. | |||
|
171 | model = {} | |||
|
172 | model['name'] = name | |||
|
173 | model['path'] = path | |||
|
174 | model['last_modified'] = last_modified | |||
|
175 | model['created'] = created | |||
|
176 | model['content'] = None | |||
|
177 | model['format'] = None | |||
|
178 | return model | |||
|
179 | ||||
|
180 | def _dir_model(self, name, path='', content=True): | |||
|
181 | """Build a model for a directory | |||
|
182 | ||||
|
183 | if content is requested, will include a listing of the directory | |||
|
184 | """ | |||
|
185 | os_path = self._get_os_path(name, path) | |||
|
186 | ||||
|
187 | four_o_four = u'directory does not exist: %r' % os_path | |||
|
188 | ||||
|
189 | if not os.path.isdir(os_path): | |||
|
190 | raise web.HTTPError(404, four_o_four) | |||
|
191 | elif is_hidden(os_path, self.root_dir): | |||
|
192 | self.log.info("Refusing to serve hidden directory %r, via 404 Error", | |||
|
193 | os_path | |||
|
194 | ) | |||
|
195 | raise web.HTTPError(404, four_o_four) | |||
|
196 | ||||
|
197 | if name is None: | |||
|
198 | if '/' in path: | |||
|
199 | path, name = path.rsplit('/', 1) | |||
|
200 | else: | |||
|
201 | name = '' | |||
|
202 | model = self._base_model(name, path) | |||
|
203 | model['type'] = 'directory' | |||
|
204 | dir_path = u'{}/{}'.format(path, name) | |||
|
205 | if content: | |||
|
206 | model['content'] = contents = [] | |||
|
207 | for os_path in glob.glob(self._get_os_path('*', dir_path)): | |||
|
208 | name = os.path.basename(os_path) | |||
|
209 | if self.should_list(name) and not is_hidden(os_path, self.root_dir): | |||
|
210 | contents.append(self.get_model(name=name, path=dir_path, content=False)) | |||
|
211 | ||||
|
212 | model['format'] = 'json' | |||
|
213 | ||||
|
214 | return model | |||
|
215 | ||||
|
216 | def _file_model(self, name, path='', content=True): | |||
|
217 | """Build a model for a file | |||
|
218 | ||||
|
219 | if content is requested, include the file contents. | |||
|
220 | UTF-8 text files will be unicode, binary files will be base64-encoded. | |||
|
221 | """ | |||
|
222 | model = self._base_model(name, path) | |||
|
223 | model['type'] = 'file' | |||
|
224 | if content: | |||
|
225 | os_path = self._get_os_path(name, path) | |||
|
226 | with io.open(os_path, 'rb') as f: | |||
|
227 | bcontent = f.read() | |||
|
228 | try: | |||
|
229 | model['content'] = bcontent.decode('utf8') | |||
|
230 | except UnicodeError as e: | |||
|
231 | model['content'] = base64.encodestring(bcontent).decode('ascii') | |||
|
232 | model['format'] = 'base64' | |||
|
233 | else: | |||
|
234 | model['format'] = 'text' | |||
|
235 | return model | |||
|
236 | ||||
|
237 | ||||
|
238 | def _notebook_model(self, name, path='', content=True): | |||
|
239 | """Build a notebook model | |||
|
240 | ||||
|
241 | if content is requested, the notebook content will be populated | |||
|
242 | as a JSON structure (not double-serialized) | |||
|
243 | """ | |||
|
244 | model = self._base_model(name, path) | |||
|
245 | model['type'] = 'notebook' | |||
|
246 | if content: | |||
|
247 | os_path = self._get_os_path(name, path) | |||
|
248 | with io.open(os_path, 'r', encoding='utf-8') as f: | |||
|
249 | try: | |||
|
250 | nb = current.read(f, u'json') | |||
|
251 | except Exception as e: | |||
|
252 | raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e)) | |||
|
253 | self.mark_trusted_cells(nb, name, path) | |||
|
254 | model['content'] = nb | |||
|
255 | model['format'] = 'json' | |||
|
256 | return model | |||
|
257 | ||||
|
258 | def get_model(self, name, path='', content=True): | |||
|
259 | """ Takes a path and name for an entity and returns its model | |||
|
260 | ||||
|
261 | Parameters | |||
|
262 | ---------- | |||
|
263 | name : str | |||
|
264 | the name of the target | |||
|
265 | path : str | |||
|
266 | the API path that describes the relative path for the target | |||
|
267 | ||||
|
268 | Returns | |||
|
269 | ------- | |||
|
270 | model : dict | |||
|
271 | the contents model. If content=True, returns the contents | |||
|
272 | of the file or directory as well. | |||
|
273 | """ | |||
|
274 | path = path.strip('/') | |||
|
275 | ||||
|
276 | if not self.exists(name=name, path=path): | |||
|
277 | raise web.HTTPError(404, u'No such file or directory: %s/%s' % (path, name)) | |||
|
278 | ||||
|
279 | os_path = self._get_os_path(name, path) | |||
|
280 | if os.path.isdir(os_path): | |||
|
281 | model = self._dir_model(name, path, content) | |||
|
282 | elif name.endswith('.ipynb'): | |||
|
283 | model = self._notebook_model(name, path, content) | |||
|
284 | else: | |||
|
285 | model = self._file_model(name, path, content) | |||
|
286 | return model | |||
|
287 | ||||
|
288 | def _save_notebook(self, os_path, model, name='', path=''): | |||
|
289 | """save a notebook file""" | |||
|
290 | # Save the notebook file | |||
|
291 | nb = current.to_notebook_json(model['content']) | |||
|
292 | ||||
|
293 | self.check_and_sign(nb, name, path) | |||
|
294 | ||||
|
295 | if 'name' in nb['metadata']: | |||
|
296 | nb['metadata']['name'] = u'' | |||
|
297 | ||||
|
298 | with io.open(os_path, 'w', encoding='utf-8') as f: | |||
|
299 | current.write(nb, f, u'json') | |||
|
300 | ||||
|
301 | def _save_file(self, os_path, model, name='', path=''): | |||
|
302 | """save a non-notebook file""" | |||
|
303 | fmt = model.get('format', None) | |||
|
304 | if fmt not in {'text', 'base64'}: | |||
|
305 | raise web.HTTPError(400, "Must specify format of file contents as 'text' or 'base64'") | |||
|
306 | try: | |||
|
307 | content = model['content'] | |||
|
308 | if fmt == 'text': | |||
|
309 | bcontent = content.encode('utf8') | |||
|
310 | else: | |||
|
311 | b64_bytes = content.encode('ascii') | |||
|
312 | bcontent = base64.decodestring(b64_bytes) | |||
|
313 | except Exception as e: | |||
|
314 | raise web.HTTPError(400, u'Encoding error saving %s: %s' % (os_path, e)) | |||
|
315 | with io.open(os_path, 'wb') as f: | |||
|
316 | f.write(bcontent) | |||
|
317 | ||||
|
318 | def _save_directory(self, os_path, model, name='', path=''): | |||
|
319 | """create a directory""" | |||
|
320 | if is_hidden(os_path, self.root_dir): | |||
|
321 | raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path) | |||
|
322 | if not os.path.exists(os_path): | |||
|
323 | os.mkdir(os_path) | |||
|
324 | elif not os.path.isdir(os_path): | |||
|
325 | raise web.HTTPError(400, u'Not a directory: %s' % (os_path)) | |||
|
326 | else: | |||
|
327 | self.log.debug("Directory %r already exists", os_path) | |||
|
328 | ||||
|
329 | def save(self, model, name='', path=''): | |||
|
330 | """Save the file model and return the model with no content.""" | |||
|
331 | path = path.strip('/') | |||
|
332 | ||||
|
333 | if 'type' not in model: | |||
|
334 | raise web.HTTPError(400, u'No file type provided') | |||
|
335 | if 'content' not in model and model['type'] != 'directory': | |||
|
336 | raise web.HTTPError(400, u'No file content provided') | |||
|
337 | ||||
|
338 | # One checkpoint should always exist | |||
|
339 | if self.file_exists(name, path) and not self.list_checkpoints(name, path): | |||
|
340 | self.create_checkpoint(name, path) | |||
|
341 | ||||
|
342 | new_path = model.get('path', path).strip('/') | |||
|
343 | new_name = model.get('name', name) | |||
|
344 | ||||
|
345 | if path != new_path or name != new_name: | |||
|
346 | self.rename(name, path, new_name, new_path) | |||
|
347 | ||||
|
348 | os_path = self._get_os_path(new_name, new_path) | |||
|
349 | self.log.debug("Saving %s", os_path) | |||
|
350 | try: | |||
|
351 | if model['type'] == 'notebook': | |||
|
352 | self._save_notebook(os_path, model, new_name, new_path) | |||
|
353 | elif model['type'] == 'file': | |||
|
354 | self._save_file(os_path, model, new_name, new_path) | |||
|
355 | elif model['type'] == 'directory': | |||
|
356 | self._save_directory(os_path, model, new_name, new_path) | |||
|
357 | else: | |||
|
358 | raise web.HTTPError(400, "Unhandled contents type: %s" % model['type']) | |||
|
359 | except web.HTTPError: | |||
|
360 | raise | |||
|
361 | except Exception as e: | |||
|
362 | raise web.HTTPError(400, u'Unexpected error while saving file: %s %s' % (os_path, e)) | |||
|
363 | ||||
|
364 | model = self.get_model(new_name, new_path, content=False) | |||
|
365 | return model | |||
|
366 | ||||
|
367 | def update(self, model, name, path=''): | |||
|
368 | """Update the file's path and/or name | |||
|
369 | ||||
|
370 | For use in PATCH requests, to enable renaming a file without | |||
|
371 | re-uploading its contents. Only used for renaming at the moment. | |||
|
372 | """ | |||
|
373 | path = path.strip('/') | |||
|
374 | new_name = model.get('name', name) | |||
|
375 | new_path = model.get('path', path).strip('/') | |||
|
376 | if path != new_path or name != new_name: | |||
|
377 | self.rename(name, path, new_name, new_path) | |||
|
378 | model = self.get_model(new_name, new_path, content=False) | |||
|
379 | return model | |||
|
380 | ||||
|
381 | def delete(self, name, path=''): | |||
|
382 | """Delete file by name and path.""" | |||
|
383 | path = path.strip('/') | |||
|
384 | os_path = self._get_os_path(name, path) | |||
|
385 | rm = os.unlink | |||
|
386 | if os.path.isdir(os_path): | |||
|
387 | listing = os.listdir(os_path) | |||
|
388 | # don't delete non-empty directories (checkpoints dir doesn't count) | |||
|
389 | if listing and listing != [self.checkpoint_dir]: | |||
|
390 | raise web.HTTPError(400, u'Directory %s not empty' % os_path) | |||
|
391 | elif not os.path.isfile(os_path): | |||
|
392 | raise web.HTTPError(404, u'File does not exist: %s' % os_path) | |||
|
393 | ||||
|
394 | # clear checkpoints | |||
|
395 | for checkpoint in self.list_checkpoints(name, path): | |||
|
396 | checkpoint_id = checkpoint['id'] | |||
|
397 | cp_path = self.get_checkpoint_path(checkpoint_id, name, path) | |||
|
398 | if os.path.isfile(cp_path): | |||
|
399 | self.log.debug("Unlinking checkpoint %s", cp_path) | |||
|
400 | os.unlink(cp_path) | |||
|
401 | ||||
|
402 | if os.path.isdir(os_path): | |||
|
403 | self.log.debug("Removing directory %s", os_path) | |||
|
404 | shutil.rmtree(os_path) | |||
|
405 | else: | |||
|
406 | self.log.debug("Unlinking file %s", os_path) | |||
|
407 | rm(os_path) | |||
|
408 | ||||
|
409 | def rename(self, old_name, old_path, new_name, new_path): | |||
|
410 | """Rename a file.""" | |||
|
411 | old_path = old_path.strip('/') | |||
|
412 | new_path = new_path.strip('/') | |||
|
413 | if new_name == old_name and new_path == old_path: | |||
|
414 | return | |||
|
415 | ||||
|
416 | new_os_path = self._get_os_path(new_name, new_path) | |||
|
417 | old_os_path = self._get_os_path(old_name, old_path) | |||
|
418 | ||||
|
419 | # Should we proceed with the move? | |||
|
420 | if os.path.isfile(new_os_path): | |||
|
421 | raise web.HTTPError(409, u'File with name already exists: %s' % new_os_path) | |||
|
422 | ||||
|
423 | # Move the file | |||
|
424 | try: | |||
|
425 | shutil.move(old_os_path, new_os_path) | |||
|
426 | except Exception as e: | |||
|
427 | raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_os_path, e)) | |||
|
428 | ||||
|
429 | # Move the checkpoints | |||
|
430 | old_checkpoints = self.list_checkpoints(old_name, old_path) | |||
|
431 | for cp in old_checkpoints: | |||
|
432 | checkpoint_id = cp['id'] | |||
|
433 | old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path) | |||
|
434 | new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path) | |||
|
435 | if os.path.isfile(old_cp_path): | |||
|
436 | self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path) | |||
|
437 | shutil.move(old_cp_path, new_cp_path) | |||
|
438 | ||||
|
439 | # Checkpoint-related utilities | |||
|
440 | ||||
|
441 | def get_checkpoint_path(self, checkpoint_id, name, path=''): | |||
|
442 | """find the path to a checkpoint""" | |||
|
443 | path = path.strip('/') | |||
|
444 | basename, ext = os.path.splitext(name) | |||
|
445 | filename = u"{name}-{checkpoint_id}{ext}".format( | |||
|
446 | name=basename, | |||
|
447 | checkpoint_id=checkpoint_id, | |||
|
448 | ext=ext, | |||
|
449 | ) | |||
|
450 | os_path = self._get_os_path(path=path) | |||
|
451 | cp_dir = os.path.join(os_path, self.checkpoint_dir) | |||
|
452 | ensure_dir_exists(cp_dir) | |||
|
453 | cp_path = os.path.join(cp_dir, filename) | |||
|
454 | return cp_path | |||
|
455 | ||||
|
456 | def get_checkpoint_model(self, checkpoint_id, name, path=''): | |||
|
457 | """construct the info dict for a given checkpoint""" | |||
|
458 | path = path.strip('/') | |||
|
459 | cp_path = self.get_checkpoint_path(checkpoint_id, name, path) | |||
|
460 | stats = os.stat(cp_path) | |||
|
461 | last_modified = tz.utcfromtimestamp(stats.st_mtime) | |||
|
462 | info = dict( | |||
|
463 | id = checkpoint_id, | |||
|
464 | last_modified = last_modified, | |||
|
465 | ) | |||
|
466 | return info | |||
|
467 | ||||
|
468 | # public checkpoint API | |||
|
469 | ||||
|
470 | def create_checkpoint(self, name, path=''): | |||
|
471 | """Create a checkpoint from the current state of a file""" | |||
|
472 | path = path.strip('/') | |||
|
473 | src_path = self._get_os_path(name, path) | |||
|
474 | # only the one checkpoint ID: | |||
|
475 | checkpoint_id = u"checkpoint" | |||
|
476 | cp_path = self.get_checkpoint_path(checkpoint_id, name, path) | |||
|
477 | self.log.debug("creating checkpoint for %s", name) | |||
|
478 | self._copy(src_path, cp_path) | |||
|
479 | ||||
|
480 | # return the checkpoint info | |||
|
481 | return self.get_checkpoint_model(checkpoint_id, name, path) | |||
|
482 | ||||
|
483 | def list_checkpoints(self, name, path=''): | |||
|
484 | """list the checkpoints for a given file | |||
|
485 | ||||
|
486 | This contents manager currently only supports one checkpoint per file. | |||
|
487 | """ | |||
|
488 | path = path.strip('/') | |||
|
489 | checkpoint_id = "checkpoint" | |||
|
490 | os_path = self.get_checkpoint_path(checkpoint_id, name, path) | |||
|
491 | if not os.path.exists(os_path): | |||
|
492 | return [] | |||
|
493 | else: | |||
|
494 | return [self.get_checkpoint_model(checkpoint_id, name, path)] | |||
|
495 | ||||
|
496 | ||||
|
497 | def restore_checkpoint(self, checkpoint_id, name, path=''): | |||
|
498 | """restore a file to a checkpointed state""" | |||
|
499 | path = path.strip('/') | |||
|
500 | self.log.info("restoring %s from checkpoint %s", name, checkpoint_id) | |||
|
501 | nb_path = self._get_os_path(name, path) | |||
|
502 | cp_path = self.get_checkpoint_path(checkpoint_id, name, path) | |||
|
503 | if not os.path.isfile(cp_path): | |||
|
504 | self.log.debug("checkpoint file does not exist: %s", cp_path) | |||
|
505 | raise web.HTTPError(404, | |||
|
506 | u'checkpoint does not exist: %s-%s' % (name, checkpoint_id) | |||
|
507 | ) | |||
|
508 | # ensure notebook is readable (never restore from an unreadable notebook) | |||
|
509 | if cp_path.endswith('.ipynb'): | |||
|
510 | with io.open(cp_path, 'r', encoding='utf-8') as f: | |||
|
511 | current.read(f, u'json') | |||
|
512 | self._copy(cp_path, nb_path) | |||
|
513 | self.log.debug("copying %s -> %s", cp_path, nb_path) | |||
|
514 | ||||
|
515 | def delete_checkpoint(self, checkpoint_id, name, path=''): | |||
|
516 | """delete a file's checkpoint""" | |||
|
517 | path = path.strip('/') | |||
|
518 | cp_path = self.get_checkpoint_path(checkpoint_id, name, path) | |||
|
519 | if not os.path.isfile(cp_path): | |||
|
520 | raise web.HTTPError(404, | |||
|
521 | u'Checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id) | |||
|
522 | ) | |||
|
523 | self.log.debug("unlinking %s", cp_path) | |||
|
524 | os.unlink(cp_path) | |||
|
525 | ||||
|
526 | def info_string(self): | |||
|
527 | return "Serving notebooks from local directory: %s" % self.root_dir | |||
|
528 | ||||
|
529 | def get_kernel_path(self, name, path='', model=None): | |||
|
530 | """Return the initial working dir a kernel associated with a given notebook""" | |||
|
531 | return os.path.join(self.root_dir, path) |
@@ -0,0 +1,286 b'' | |||||
|
1 | """Tornado handlers for the contents web service.""" | |||
|
2 | ||||
|
3 | # Copyright (c) IPython Development Team. | |||
|
4 | # Distributed under the terms of the Modified BSD License. | |||
|
5 | ||||
|
6 | import json | |||
|
7 | ||||
|
8 | from tornado import web | |||
|
9 | ||||
|
10 | from IPython.html.utils import url_path_join, url_escape | |||
|
11 | from IPython.utils.jsonutil import date_default | |||
|
12 | ||||
|
13 | from IPython.html.base.handlers import (IPythonHandler, json_errors, | |||
|
14 | file_path_regex, path_regex, | |||
|
15 | file_name_regex) | |||
|
16 | ||||
|
17 | ||||
|
18 | def sort_key(model): | |||
|
19 | """key function for case-insensitive sort by name and type""" | |||
|
20 | iname = model['name'].lower() | |||
|
21 | type_key = { | |||
|
22 | 'directory' : '0', | |||
|
23 | 'notebook' : '1', | |||
|
24 | 'file' : '2', | |||
|
25 | }.get(model['type'], '9') | |||
|
26 | return u'%s%s' % (type_key, iname) | |||
|
27 | ||||
|
28 | class ContentsHandler(IPythonHandler): | |||
|
29 | ||||
|
30 | SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE') | |||
|
31 | ||||
|
32 | def location_url(self, name, path): | |||
|
33 | """Return the full URL location of a file. | |||
|
34 | ||||
|
35 | Parameters | |||
|
36 | ---------- | |||
|
37 | name : unicode | |||
|
38 | The base name of the file, such as "foo.ipynb". | |||
|
39 | path : unicode | |||
|
40 | The API path of the file, such as "foo/bar". | |||
|
41 | """ | |||
|
42 | return url_escape(url_path_join( | |||
|
43 | self.base_url, 'api', 'contents', path, name | |||
|
44 | )) | |||
|
45 | ||||
|
46 | def _finish_model(self, model, location=True): | |||
|
47 | """Finish a JSON request with a model, setting relevant headers, etc.""" | |||
|
48 | if location: | |||
|
49 | location = self.location_url(model['name'], model['path']) | |||
|
50 | self.set_header('Location', location) | |||
|
51 | self.set_header('Last-Modified', model['last_modified']) | |||
|
52 | self.finish(json.dumps(model, default=date_default)) | |||
|
53 | ||||
|
54 | @web.authenticated | |||
|
55 | @json_errors | |||
|
56 | def get(self, path='', name=None): | |||
|
57 | """Return a model for a file or directory. | |||
|
58 | ||||
|
59 | A directory model contains a list of models (without content) | |||
|
60 | of the files and directories it contains. | |||
|
61 | """ | |||
|
62 | path = path or '' | |||
|
63 | model = self.contents_manager.get_model(name=name, path=path) | |||
|
64 | if model['type'] == 'directory': | |||
|
65 | # group listing by type, then by name (case-insensitive) | |||
|
66 | # FIXME: sorting should be done in the frontends | |||
|
67 | model['content'].sort(key=sort_key) | |||
|
68 | self._finish_model(model, location=False) | |||
|
69 | ||||
|
70 | @web.authenticated | |||
|
71 | @json_errors | |||
|
72 | def patch(self, path='', name=None): | |||
|
73 | """PATCH renames a notebook without re-uploading content.""" | |||
|
74 | cm = self.contents_manager | |||
|
75 | if name is None: | |||
|
76 | raise web.HTTPError(400, u'Filename missing') | |||
|
77 | model = self.get_json_body() | |||
|
78 | if model is None: | |||
|
79 | raise web.HTTPError(400, u'JSON body missing') | |||
|
80 | model = cm.update(model, name, path) | |||
|
81 | self._finish_model(model) | |||
|
82 | ||||
|
83 | def _copy(self, copy_from, path, copy_to=None): | |||
|
84 | """Copy a file, optionally specifying the new name. | |||
|
85 | """ | |||
|
86 | self.log.info(u"Copying {copy_from} to {path}/{copy_to}".format( | |||
|
87 | copy_from=copy_from, | |||
|
88 | path=path, | |||
|
89 | copy_to=copy_to or '', | |||
|
90 | )) | |||
|
91 | model = self.contents_manager.copy(copy_from, copy_to, path) | |||
|
92 | self.set_status(201) | |||
|
93 | self._finish_model(model) | |||
|
94 | ||||
|
95 | def _upload(self, model, path, name=None): | |||
|
96 | """Handle upload of a new file | |||
|
97 | ||||
|
98 | If name specified, create it in path/name, | |||
|
99 | otherwise create a new untitled file in path. | |||
|
100 | """ | |||
|
101 | self.log.info(u"Uploading file to %s/%s", path, name or '') | |||
|
102 | if name: | |||
|
103 | model['name'] = name | |||
|
104 | ||||
|
105 | model = self.contents_manager.create_file(model, path) | |||
|
106 | self.set_status(201) | |||
|
107 | self._finish_model(model) | |||
|
108 | ||||
|
109 | def _create_empty_file(self, path, name=None, ext='.ipynb'): | |||
|
110 | """Create an empty file in path | |||
|
111 | ||||
|
112 | If name specified, create it in path/name. | |||
|
113 | """ | |||
|
114 | self.log.info(u"Creating new file in %s/%s", path, name or '') | |||
|
115 | model = {} | |||
|
116 | if name: | |||
|
117 | model['name'] = name | |||
|
118 | model = self.contents_manager.create_file(model, path=path, ext=ext) | |||
|
119 | self.set_status(201) | |||
|
120 | self._finish_model(model) | |||
|
121 | ||||
|
122 | def _save(self, model, path, name): | |||
|
123 | """Save an existing file.""" | |||
|
124 | self.log.info(u"Saving file at %s/%s", path, name) | |||
|
125 | model = self.contents_manager.save(model, name, path) | |||
|
126 | if model['path'] != path.strip('/') or model['name'] != name: | |||
|
127 | # a rename happened, set Location header | |||
|
128 | location = True | |||
|
129 | else: | |||
|
130 | location = False | |||
|
131 | self._finish_model(model, location) | |||
|
132 | ||||
|
133 | @web.authenticated | |||
|
134 | @json_errors | |||
|
135 | def post(self, path='', name=None): | |||
|
136 | """Create a new file or directory in the specified path. | |||
|
137 | ||||
|
138 | POST creates new files or directories. The server always decides on the name. | |||
|
139 | ||||
|
140 | POST /api/contents/path | |||
|
141 | New untitled notebook in path. If content specified, upload a | |||
|
142 | notebook, otherwise start empty. | |||
|
143 | POST /api/contents/path | |||
|
144 | with body {"copy_from" : "OtherNotebook.ipynb"} | |||
|
145 | New copy of OtherNotebook in path | |||
|
146 | """ | |||
|
147 | ||||
|
148 | if name is not None: | |||
|
149 | path = u'{}/{}'.format(path, name) | |||
|
150 | ||||
|
151 | cm = self.contents_manager | |||
|
152 | ||||
|
153 | if cm.file_exists(path): | |||
|
154 | raise web.HTTPError(400, "Cannot POST to existing files, use PUT instead.") | |||
|
155 | ||||
|
156 | if not cm.path_exists(path): | |||
|
157 | raise web.HTTPError(404, "No such directory: %s" % path) | |||
|
158 | ||||
|
159 | model = self.get_json_body() | |||
|
160 | ||||
|
161 | if model is not None: | |||
|
162 | copy_from = model.get('copy_from') | |||
|
163 | ext = model.get('ext', '.ipynb') | |||
|
164 | if model.get('content') is not None: | |||
|
165 | if copy_from: | |||
|
166 | raise web.HTTPError(400, "Can't upload and copy at the same time.") | |||
|
167 | self._upload(model, path) | |||
|
168 | elif copy_from: | |||
|
169 | self._copy(copy_from, path) | |||
|
170 | else: | |||
|
171 | self._create_empty_file(path, ext=ext) | |||
|
172 | else: | |||
|
173 | self._create_empty_file(path) | |||
|
174 | ||||
|
175 | @web.authenticated | |||
|
176 | @json_errors | |||
|
177 | def put(self, path='', name=None): | |||
|
178 | """Saves the file in the location specified by name and path. | |||
|
179 | ||||
|
180 | PUT is very similar to POST, but the requester specifies the name, | |||
|
181 | whereas with POST, the server picks the name. | |||
|
182 | ||||
|
183 | PUT /api/contents/path/Name.ipynb | |||
|
184 | Save notebook at ``path/Name.ipynb``. Notebook structure is specified | |||
|
185 | in `content` key of JSON request body. If content is not specified, | |||
|
186 | create a new empty notebook. | |||
|
187 | PUT /api/contents/path/Name.ipynb | |||
|
188 | with JSON body:: | |||
|
189 | ||||
|
190 | { | |||
|
191 | "copy_from" : "[path/to/]OtherNotebook.ipynb" | |||
|
192 | } | |||
|
193 | ||||
|
194 | Copy OtherNotebook to Name | |||
|
195 | """ | |||
|
196 | if name is None: | |||
|
197 | raise web.HTTPError(400, "name must be specified with PUT.") | |||
|
198 | ||||
|
199 | model = self.get_json_body() | |||
|
200 | if model: | |||
|
201 | copy_from = model.get('copy_from') | |||
|
202 | if copy_from: | |||
|
203 | if model.get('content'): | |||
|
204 | raise web.HTTPError(400, "Can't upload and copy at the same time.") | |||
|
205 | self._copy(copy_from, path, name) | |||
|
206 | elif self.contents_manager.file_exists(name, path): | |||
|
207 | self._save(model, path, name) | |||
|
208 | else: | |||
|
209 | self._upload(model, path, name) | |||
|
210 | else: | |||
|
211 | self._create_empty_file(path, name) | |||
|
212 | ||||
|
213 | @web.authenticated | |||
|
214 | @json_errors | |||
|
215 | def delete(self, path='', name=None): | |||
|
216 | """delete a file in the given path""" | |||
|
217 | cm = self.contents_manager | |||
|
218 | self.log.warn('delete %s:%s', path, name) | |||
|
219 | cm.delete(name, path) | |||
|
220 | self.set_status(204) | |||
|
221 | self.finish() | |||
|
222 | ||||
|
223 | ||||
|
224 | class CheckpointsHandler(IPythonHandler): | |||
|
225 | ||||
|
226 | SUPPORTED_METHODS = ('GET', 'POST') | |||
|
227 | ||||
|
228 | @web.authenticated | |||
|
229 | @json_errors | |||
|
230 | def get(self, path='', name=None): | |||
|
231 | """get lists checkpoints for a file""" | |||
|
232 | cm = self.contents_manager | |||
|
233 | checkpoints = cm.list_checkpoints(name, path) | |||
|
234 | data = json.dumps(checkpoints, default=date_default) | |||
|
235 | self.finish(data) | |||
|
236 | ||||
|
237 | @web.authenticated | |||
|
238 | @json_errors | |||
|
239 | def post(self, path='', name=None): | |||
|
240 | """post creates a new checkpoint""" | |||
|
241 | cm = self.contents_manager | |||
|
242 | checkpoint = cm.create_checkpoint(name, path) | |||
|
243 | data = json.dumps(checkpoint, default=date_default) | |||
|
244 | location = url_path_join(self.base_url, 'api/contents', | |||
|
245 | path, name, 'checkpoints', checkpoint['id']) | |||
|
246 | self.set_header('Location', url_escape(location)) | |||
|
247 | self.set_status(201) | |||
|
248 | self.finish(data) | |||
|
249 | ||||
|
250 | ||||
|
251 | class ModifyCheckpointsHandler(IPythonHandler): | |||
|
252 | ||||
|
253 | SUPPORTED_METHODS = ('POST', 'DELETE') | |||
|
254 | ||||
|
255 | @web.authenticated | |||
|
256 | @json_errors | |||
|
257 | def post(self, path, name, checkpoint_id): | |||
|
258 | """post restores a file from a checkpoint""" | |||
|
259 | cm = self.contents_manager | |||
|
260 | cm.restore_checkpoint(checkpoint_id, name, path) | |||
|
261 | self.set_status(204) | |||
|
262 | self.finish() | |||
|
263 | ||||
|
264 | @web.authenticated | |||
|
265 | @json_errors | |||
|
266 | def delete(self, path, name, checkpoint_id): | |||
|
267 | """delete clears a checkpoint for a given file""" | |||
|
268 | cm = self.contents_manager | |||
|
269 | cm.delete_checkpoint(checkpoint_id, name, path) | |||
|
270 | self.set_status(204) | |||
|
271 | self.finish() | |||
|
272 | ||||
|
273 | #----------------------------------------------------------------------------- | |||
|
274 | # URL to handler mappings | |||
|
275 | #----------------------------------------------------------------------------- | |||
|
276 | ||||
|
277 | ||||
|
278 | _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)" | |||
|
279 | ||||
|
280 | default_handlers = [ | |||
|
281 | (r"/api/contents%s/checkpoints" % file_path_regex, CheckpointsHandler), | |||
|
282 | (r"/api/contents%s/checkpoints/%s" % (file_path_regex, _checkpoint_id_regex), | |||
|
283 | ModifyCheckpointsHandler), | |||
|
284 | (r"/api/contents%s" % file_path_regex, ContentsHandler), | |||
|
285 | (r"/api/contents%s" % path_regex, ContentsHandler), | |||
|
286 | ] |
@@ -0,0 +1,333 b'' | |||||
|
1 | """A base class for contents managers.""" | |||
|
2 | ||||
|
3 | # Copyright (c) IPython Development Team. | |||
|
4 | # Distributed under the terms of the Modified BSD License. | |||
|
5 | ||||
|
6 | from fnmatch import fnmatch | |||
|
7 | import itertools | |||
|
8 | import os | |||
|
9 | ||||
|
10 | from tornado.web import HTTPError | |||
|
11 | ||||
|
12 | from IPython.config.configurable import LoggingConfigurable | |||
|
13 | from IPython.nbformat import current, sign | |||
|
14 | from IPython.utils.traitlets import Instance, Unicode, List | |||
|
15 | ||||
|
16 | ||||
|
17 | class ContentsManager(LoggingConfigurable): | |||
|
18 | """Base class for serving files and directories. | |||
|
19 | ||||
|
20 | This serves any text or binary file, | |||
|
21 | as well as directories, | |||
|
22 | with special handling for JSON notebook documents. | |||
|
23 | ||||
|
24 | Most APIs take a path argument, | |||
|
25 | which is always an API-style unicode path, | |||
|
26 | and always refers to a directory. | |||
|
27 | ||||
|
28 | - unicode, not url-escaped | |||
|
29 | - '/'-separated | |||
|
30 | - leading and trailing '/' will be stripped | |||
|
31 | - if unspecified, path defaults to '', | |||
|
32 | indicating the root path. | |||
|
33 | ||||
|
34 | name is also unicode, and refers to a specfic target: | |||
|
35 | ||||
|
36 | - unicode, not url-escaped | |||
|
37 | - must not contain '/' | |||
|
38 | - It refers to an individual filename | |||
|
39 | - It may refer to a directory name, | |||
|
40 | in the case of listing or creating directories. | |||
|
41 | ||||
|
42 | """ | |||
|
43 | ||||
|
44 | notary = Instance(sign.NotebookNotary) | |||
|
45 | def _notary_default(self): | |||
|
46 | return sign.NotebookNotary(parent=self) | |||
|
47 | ||||
|
48 | hide_globs = List(Unicode, [ | |||
|
49 | u'__pycache__', '*.pyc', '*.pyo', | |||
|
50 | '.DS_Store', '*.so', '*.dylib', '*~', | |||
|
51 | ], config=True, help=""" | |||
|
52 | Glob patterns to hide in file and directory listings. | |||
|
53 | """) | |||
|
54 | ||||
|
55 | untitled_notebook = Unicode("Untitled", config=True, | |||
|
56 | help="The base name used when creating untitled notebooks." | |||
|
57 | ) | |||
|
58 | ||||
|
59 | untitled_file = Unicode("untitled", config=True, | |||
|
60 | help="The base name used when creating untitled files." | |||
|
61 | ) | |||
|
62 | ||||
|
63 | untitled_directory = Unicode("Untitled Folder", config=True, | |||
|
64 | help="The base name used when creating untitled directories." | |||
|
65 | ) | |||
|
66 | ||||
|
67 | # ContentsManager API part 1: methods that must be | |||
|
68 | # implemented in subclasses. | |||
|
69 | ||||
|
70 | def path_exists(self, path): | |||
|
71 | """Does the API-style path (directory) actually exist? | |||
|
72 | ||||
|
73 | Like os.path.isdir | |||
|
74 | ||||
|
75 | Override this method in subclasses. | |||
|
76 | ||||
|
77 | Parameters | |||
|
78 | ---------- | |||
|
79 | path : string | |||
|
80 | The path to check | |||
|
81 | ||||
|
82 | Returns | |||
|
83 | ------- | |||
|
84 | exists : bool | |||
|
85 | Whether the path does indeed exist. | |||
|
86 | """ | |||
|
87 | raise NotImplementedError | |||
|
88 | ||||
|
89 | def is_hidden(self, path): | |||
|
90 | """Does the API style path correspond to a hidden directory or file? | |||
|
91 | ||||
|
92 | Parameters | |||
|
93 | ---------- | |||
|
94 | path : string | |||
|
95 | The path to check. This is an API path (`/` separated, | |||
|
96 | relative to root dir). | |||
|
97 | ||||
|
98 | Returns | |||
|
99 | ------- | |||
|
100 | hidden : bool | |||
|
101 | Whether the path is hidden. | |||
|
102 | ||||
|
103 | """ | |||
|
104 | raise NotImplementedError | |||
|
105 | ||||
|
106 | def file_exists(self, name, path=''): | |||
|
107 | """Does a file exist at the given name and path? | |||
|
108 | ||||
|
109 | Like os.path.isfile | |||
|
110 | ||||
|
111 | Override this method in subclasses. | |||
|
112 | ||||
|
113 | Parameters | |||
|
114 | ---------- | |||
|
115 | name : string | |||
|
116 | The name of the file you are checking. | |||
|
117 | path : string | |||
|
118 | The relative path to the file's directory (with '/' as separator) | |||
|
119 | ||||
|
120 | Returns | |||
|
121 | ------- | |||
|
122 | exists : bool | |||
|
123 | Whether the file exists. | |||
|
124 | """ | |||
|
125 | raise NotImplementedError('must be implemented in a subclass') | |||
|
126 | ||||
|
127 | def exists(self, name, path=''): | |||
|
128 | """Does a file or directory exist at the given name and path? | |||
|
129 | ||||
|
130 | Like os.path.exists | |||
|
131 | ||||
|
132 | Parameters | |||
|
133 | ---------- | |||
|
134 | name : string | |||
|
135 | The name of the file you are checking. | |||
|
136 | path : string | |||
|
137 | The relative path to the file's directory (with '/' as separator) | |||
|
138 | ||||
|
139 | Returns | |||
|
140 | ------- | |||
|
141 | exists : bool | |||
|
142 | Whether the target exists. | |||
|
143 | """ | |||
|
144 | return self.file_exists(name, path) or self.path_exists("%s/%s" % (path, name)) | |||
|
145 | ||||
|
146 | def get_model(self, name, path='', content=True): | |||
|
147 | """Get the model of a file or directory with or without content.""" | |||
|
148 | raise NotImplementedError('must be implemented in a subclass') | |||
|
149 | ||||
|
150 | def save(self, model, name, path=''): | |||
|
151 | """Save the file or directory and return the model with no content.""" | |||
|
152 | raise NotImplementedError('must be implemented in a subclass') | |||
|
153 | ||||
|
154 | def update(self, model, name, path=''): | |||
|
155 | """Update the file or directory and return the model with no content. | |||
|
156 | ||||
|
157 | For use in PATCH requests, to enable renaming a file without | |||
|
158 | re-uploading its contents. Only used for renaming at the moment. | |||
|
159 | """ | |||
|
160 | raise NotImplementedError('must be implemented in a subclass') | |||
|
161 | ||||
|
162 | def delete(self, name, path=''): | |||
|
163 | """Delete file or directory by name and path.""" | |||
|
164 | raise NotImplementedError('must be implemented in a subclass') | |||
|
165 | ||||
|
166 | def create_checkpoint(self, name, path=''): | |||
|
167 | """Create a checkpoint of the current state of a file | |||
|
168 | ||||
|
169 | Returns a checkpoint_id for the new checkpoint. | |||
|
170 | """ | |||
|
171 | raise NotImplementedError("must be implemented in a subclass") | |||
|
172 | ||||
|
173 | def list_checkpoints(self, name, path=''): | |||
|
174 | """Return a list of checkpoints for a given file""" | |||
|
175 | return [] | |||
|
176 | ||||
|
177 | def restore_checkpoint(self, checkpoint_id, name, path=''): | |||
|
178 | """Restore a file from one of its checkpoints""" | |||
|
179 | raise NotImplementedError("must be implemented in a subclass") | |||
|
180 | ||||
|
181 | def delete_checkpoint(self, checkpoint_id, name, path=''): | |||
|
182 | """delete a checkpoint for a file""" | |||
|
183 | raise NotImplementedError("must be implemented in a subclass") | |||
|
184 | ||||
|
185 | # ContentsManager API part 2: methods that have useable default | |||
|
186 | # implementations, but can be overridden in subclasses. | |||
|
187 | ||||
|
188 | def info_string(self): | |||
|
189 | return "Serving contents" | |||
|
190 | ||||
|
191 | def get_kernel_path(self, name, path='', model=None): | |||
|
192 | """ Return the path to start kernel in """ | |||
|
193 | return path | |||
|
194 | ||||
|
195 | def increment_filename(self, filename, path=''): | |||
|
196 | """Increment a filename until it is unique. | |||
|
197 | ||||
|
198 | Parameters | |||
|
199 | ---------- | |||
|
200 | filename : unicode | |||
|
201 | The name of a file, including extension | |||
|
202 | path : unicode | |||
|
203 | The API path of the target's directory | |||
|
204 | ||||
|
205 | Returns | |||
|
206 | ------- | |||
|
207 | name : unicode | |||
|
208 | A filename that is unique, based on the input filename. | |||
|
209 | """ | |||
|
210 | path = path.strip('/') | |||
|
211 | basename, ext = os.path.splitext(filename) | |||
|
212 | for i in itertools.count(): | |||
|
213 | name = u'{basename}{i}{ext}'.format(basename=basename, i=i, | |||
|
214 | ext=ext) | |||
|
215 | if not self.file_exists(name, path): | |||
|
216 | break | |||
|
217 | return name | |||
|
218 | ||||
|
219 | def create_file(self, model=None, path='', ext='.ipynb'): | |||
|
220 | """Create a new file or directory and return its model with no content.""" | |||
|
221 | path = path.strip('/') | |||
|
222 | if model is None: | |||
|
223 | model = {} | |||
|
224 | if 'content' not in model and model.get('type', None) != 'directory': | |||
|
225 | if ext == '.ipynb': | |||
|
226 | metadata = current.new_metadata(name=u'') | |||
|
227 | model['content'] = current.new_notebook(metadata=metadata) | |||
|
228 | model['type'] = 'notebook' | |||
|
229 | model['format'] = 'json' | |||
|
230 | else: | |||
|
231 | model['content'] = '' | |||
|
232 | model['type'] = 'file' | |||
|
233 | model['format'] = 'text' | |||
|
234 | if 'name' not in model: | |||
|
235 | if model['type'] == 'directory': | |||
|
236 | untitled = self.untitled_directory | |||
|
237 | elif model['type'] == 'notebook': | |||
|
238 | untitled = self.untitled_notebook | |||
|
239 | elif model['type'] == 'file': | |||
|
240 | untitled = self.untitled_file | |||
|
241 | else: | |||
|
242 | raise HTTPError(400, "Unexpected model type: %r" % model['type']) | |||
|
243 | model['name'] = self.increment_filename(untitled + ext, path) | |||
|
244 | ||||
|
245 | model['path'] = path | |||
|
246 | model = self.save(model, model['name'], model['path']) | |||
|
247 | return model | |||
|
248 | ||||
|
249 | def copy(self, from_name, to_name=None, path=''): | |||
|
250 | """Copy an existing file and return its new model. | |||
|
251 | ||||
|
252 | If to_name not specified, increment `from_name-Copy#.ext`. | |||
|
253 | ||||
|
254 | copy_from can be a full path to a file, | |||
|
255 | or just a base name. If a base name, `path` is used. | |||
|
256 | """ | |||
|
257 | path = path.strip('/') | |||
|
258 | if '/' in from_name: | |||
|
259 | from_path, from_name = from_name.rsplit('/', 1) | |||
|
260 | else: | |||
|
261 | from_path = path | |||
|
262 | model = self.get_model(from_name, from_path) | |||
|
263 | if model['type'] == 'directory': | |||
|
264 | raise HTTPError(400, "Can't copy directories") | |||
|
265 | if not to_name: | |||
|
266 | base, ext = os.path.splitext(from_name) | |||
|
267 | copy_name = u'{0}-Copy{1}'.format(base, ext) | |||
|
268 | to_name = self.increment_filename(copy_name, path) | |||
|
269 | model['name'] = to_name | |||
|
270 | model['path'] = path | |||
|
271 | model = self.save(model, to_name, path) | |||
|
272 | return model | |||
|
273 | ||||
|
274 | def log_info(self): | |||
|
275 | self.log.info(self.info_string()) | |||
|
276 | ||||
|
277 | def trust_notebook(self, name, path=''): | |||
|
278 | """Explicitly trust a notebook | |||
|
279 | ||||
|
280 | Parameters | |||
|
281 | ---------- | |||
|
282 | name : string | |||
|
283 | The filename of the notebook | |||
|
284 | path : string | |||
|
285 | The notebook's directory | |||
|
286 | """ | |||
|
287 | model = self.get_model(name, path) | |||
|
288 | nb = model['content'] | |||
|
289 | self.log.warn("Trusting notebook %s/%s", path, name) | |||
|
290 | self.notary.mark_cells(nb, True) | |||
|
291 | self.save(model, name, path) | |||
|
292 | ||||
|
293 | def check_and_sign(self, nb, name='', path=''): | |||
|
294 | """Check for trusted cells, and sign the notebook. | |||
|
295 | ||||
|
296 | Called as a part of saving notebooks. | |||
|
297 | ||||
|
298 | Parameters | |||
|
299 | ---------- | |||
|
300 | nb : dict | |||
|
301 | The notebook object (in nbformat.current format) | |||
|
302 | name : string | |||
|
303 | The filename of the notebook (for logging) | |||
|
304 | path : string | |||
|
305 | The notebook's directory (for logging) | |||
|
306 | """ | |||
|
307 | if self.notary.check_cells(nb): | |||
|
308 | self.notary.sign(nb) | |||
|
309 | else: | |||
|
310 | self.log.warn("Saving untrusted notebook %s/%s", path, name) | |||
|
311 | ||||
|
312 | def mark_trusted_cells(self, nb, name='', path=''): | |||
|
313 | """Mark cells as trusted if the notebook signature matches. | |||
|
314 | ||||
|
315 | Called as a part of loading notebooks. | |||
|
316 | ||||
|
317 | Parameters | |||
|
318 | ---------- | |||
|
319 | nb : dict | |||
|
320 | The notebook object (in nbformat.current format) | |||
|
321 | name : string | |||
|
322 | The filename of the notebook (for logging) | |||
|
323 | path : string | |||
|
324 | The notebook's directory (for logging) | |||
|
325 | """ | |||
|
326 | trusted = self.notary.check_signature(nb) | |||
|
327 | if not trusted: | |||
|
328 | self.log.warn("Notebook %s/%s is not trusted", path, name) | |||
|
329 | self.notary.mark_cells(nb, trusted) | |||
|
330 | ||||
|
331 | def should_list(self, name): | |||
|
332 | """Should this file/directory name be displayed in a listing?""" | |||
|
333 | return not any(fnmatch(name, glob) for glob in self.hide_globs) |
@@ -0,0 +1,91 b'' | |||||
|
1 | // Copyright (c) IPython Development Team. | |||
|
2 | // Distributed under the terms of the Modified BSD License. | |||
|
3 | ||||
|
4 | define([ | |||
|
5 | 'base/js/namespace', | |||
|
6 | 'jquery', | |||
|
7 | 'base/js/utils', | |||
|
8 | ], function(IPython, $, utils) { | |||
|
9 | "use strict"; | |||
|
10 | ||||
|
11 | var KernelSelector = function(selector, notebook) { | |||
|
12 | this.selector = selector; | |||
|
13 | this.notebook = notebook; | |||
|
14 | this.events = notebook.events; | |||
|
15 | this.current_selection = notebook.default_kernel_name; | |||
|
16 | this.kernelspecs = {}; | |||
|
17 | if (this.selector !== undefined) { | |||
|
18 | this.element = $(selector); | |||
|
19 | this.request_kernelspecs(); | |||
|
20 | } | |||
|
21 | this.bind_events(); | |||
|
22 | // Make the object globally available for user convenience & inspection | |||
|
23 | IPython.kernelselector = this; | |||
|
24 | }; | |||
|
25 | ||||
|
26 | KernelSelector.prototype.request_kernelspecs = function() { | |||
|
27 | var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs'); | |||
|
28 | $.ajax(url, {success: $.proxy(this._got_kernelspecs, this)}); | |||
|
29 | }; | |||
|
30 | ||||
|
31 | KernelSelector.prototype._got_kernelspecs = function(data, status, xhr) { | |||
|
32 | this.kernelspecs = {}; | |||
|
33 | var menu = this.element.find("#kernel_selector"); | |||
|
34 | var change_kernel_submenu = $("#menu-change-kernel-submenu"); | |||
|
35 | for (var i = 0; i < data.length; i++) { | |||
|
36 | var ks = data[i]; | |||
|
37 | this.kernelspecs[ks.name] = ks; | |||
|
38 | var ksentry = $("<li>").attr("id", "kernel-" +ks.name).append($('<a>') | |||
|
39 | .attr('href', '#') | |||
|
40 | .click($.proxy(this.change_kernel, this, ks.name)) | |||
|
41 | .text(ks.display_name)); | |||
|
42 | menu.append(ksentry); | |||
|
43 | ||||
|
44 | var ks_submenu_entry = $("<li>").attr("id", "kernel-submenu-"+ks.name).append($('<a>') | |||
|
45 | .attr('href', '#') | |||
|
46 | .click($.proxy(this.change_kernel, this, ks.name)) | |||
|
47 | .text(ks.display_name)); | |||
|
48 | change_kernel_submenu.append(ks_submenu_entry); | |||
|
49 | } | |||
|
50 | }; | |||
|
51 | ||||
|
52 | KernelSelector.prototype.change_kernel = function(kernel_name) { | |||
|
53 | if (kernel_name === this.current_selection) { | |||
|
54 | return; | |||
|
55 | } | |||
|
56 | var ks = this.kernelspecs[kernel_name]; | |||
|
57 | try { | |||
|
58 | this.notebook.start_session(kernel_name); | |||
|
59 | } catch (e) { | |||
|
60 | if (e.name === 'SessionAlreadyStarting') { | |||
|
61 | console.log("Cannot change kernel while waiting for pending session start."); | |||
|
62 | } else { | |||
|
63 | // unhandled error | |||
|
64 | throw e; | |||
|
65 | } | |||
|
66 | // only trigger spec_changed if change was successful | |||
|
67 | return; | |||
|
68 | } | |||
|
69 | this.events.trigger('spec_changed.Kernel', ks); | |||
|
70 | }; | |||
|
71 | ||||
|
72 | KernelSelector.prototype.bind_events = function() { | |||
|
73 | var that = this; | |||
|
74 | this.events.on('spec_changed.Kernel', function(event, data) { | |||
|
75 | that.current_selection = data.name; | |||
|
76 | that.element.find("#current_kernel_spec").find('.kernel_name').text(data.display_name); | |||
|
77 | }); | |||
|
78 | ||||
|
79 | this.events.on('started.Session', function(events, session) { | |||
|
80 | if (session.kernel_name !== that.current_selection) { | |||
|
81 | // If we created a 'python' session, we only know if it's Python | |||
|
82 | // 3 or 2 on the server's reply, so we fire the event again to | |||
|
83 | // set things up. | |||
|
84 | var ks = that.kernelspecs[session.kernel_name]; | |||
|
85 | that.events.trigger('spec_changed.Kernel', ks); | |||
|
86 | } | |||
|
87 | }); | |||
|
88 | }; | |||
|
89 | ||||
|
90 | return {'KernelSelector': KernelSelector}; | |||
|
91 | }); |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100755 |
|
NO CONTENT: new file 100755 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644, binary diff hidden |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 120000 |
|
NO CONTENT: new file 120000 | ||
The requested commit or file is too big and content was truncated. Show full diff |
@@ -7,6 +7,7 b' docs/source/api/generated' | |||||
7 | docs/source/config/options |
|
7 | docs/source/config/options | |
8 | docs/gh-pages |
|
8 | docs/gh-pages | |
9 | IPython/html/notebook/static/mathjax |
|
9 | IPython/html/notebook/static/mathjax | |
|
10 | IPython/html/static/style/*.map | |||
10 | *.py[co] |
|
11 | *.py[co] | |
11 | __pycache__ |
|
12 | __pycache__ | |
12 | *.egg-info |
|
13 | *.egg-info |
@@ -13,8 +13,10 b' before_install:' | |||||
13 | # Pierre Carrier's PPA for PhantomJS and CasperJS |
|
13 | # Pierre Carrier's PPA for PhantomJS and CasperJS | |
14 | - time sudo add-apt-repository -y ppa:pcarrier/ppa |
|
14 | - time sudo add-apt-repository -y ppa:pcarrier/ppa | |
15 | - time sudo apt-get update |
|
15 | - time sudo apt-get update | |
16 |
- time sudo apt-get install pandoc casperjs |
|
16 | - time sudo apt-get install pandoc casperjs libzmq3-dev | |
17 | - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests mock pyzmq jsonschema jsonpointer |
|
17 | # pin tornado < 4 for js tests while phantom is on super old webkit | |
|
18 | - if [[ $GROUP == 'js' ]]; then pip install 'tornado<4'; fi | |||
|
19 | - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests mock pyzmq jsonschema jsonpointer mistune | |||
18 | install: |
|
20 | install: | |
19 | - time python setup.py install -q |
|
21 | - time python setup.py install -q | |
20 | script: |
|
22 | script: |
@@ -1,31 +1,11 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """A base class for objects that are configurable.""" | |
3 | A base class for objects that are configurable. |
|
|||
4 |
|
3 | |||
5 | Inheritance diagram: |
|
4 | # Copyright (c) IPython Development Team. | |
|
5 | # Distributed under the terms of the Modified BSD License. | |||
6 |
|
6 | |||
7 | .. inheritance-diagram:: IPython.config.configurable |
|
|||
8 | :parts: 3 |
|
|||
9 |
|
||||
10 | Authors: |
|
|||
11 |
|
||||
12 | * Brian Granger |
|
|||
13 | * Fernando Perez |
|
|||
14 | * Min RK |
|
|||
15 | """ |
|
|||
16 | from __future__ import print_function |
|
7 | from __future__ import print_function | |
17 |
|
8 | |||
18 | #----------------------------------------------------------------------------- |
|
|||
19 | # Copyright (C) 2008-2011 The IPython Development Team |
|
|||
20 | # |
|
|||
21 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
22 | # the file COPYING, distributed as part of this software. |
|
|||
23 | #----------------------------------------------------------------------------- |
|
|||
24 |
|
||||
25 | #----------------------------------------------------------------------------- |
|
|||
26 | # Imports |
|
|||
27 | #----------------------------------------------------------------------------- |
|
|||
28 |
|
||||
29 | import logging |
|
9 | import logging | |
30 | from copy import deepcopy |
|
10 | from copy import deepcopy | |
31 |
|
11 | |||
@@ -375,16 +355,12 b' class LoggingConfigurable(Configurable):' | |||||
375 | """A parent class for Configurables that log. |
|
355 | """A parent class for Configurables that log. | |
376 |
|
356 | |||
377 | Subclasses have a log trait, and the default behavior |
|
357 | Subclasses have a log trait, and the default behavior | |
378 | is to get the logger from the currently running Application |
|
358 | is to get the logger from the currently running Application. | |
379 | via Application.instance().log. |
|
|||
380 | """ |
|
359 | """ | |
381 |
|
360 | |||
382 | log = Instance('logging.Logger') |
|
361 | log = Instance('logging.Logger') | |
383 | def _log_default(self): |
|
362 | def _log_default(self): | |
384 |
from IPython. |
|
363 | from IPython.utils import log | |
385 | if Application.initialized(): |
|
364 | return log.get_logger() | |
386 | return Application.instance().log |
|
|||
387 | else: |
|
|||
388 | return logging.getLogger() |
|
|||
389 |
|
365 | |||
390 |
|
366 |
@@ -1,27 +1,8 b'' | |||||
1 | """A simple configuration system. |
|
1 | # encoding: utf-8 | |
|
2 | """A simple configuration system.""" | |||
2 |
|
3 | |||
3 | Inheritance diagram: |
|
4 | # Copyright (c) IPython Development Team. | |
4 |
|
5 | # Distributed under the terms of the Modified BSD License. | ||
5 | .. inheritance-diagram:: IPython.config.loader |
|
|||
6 | :parts: 3 |
|
|||
7 |
|
||||
8 | Authors |
|
|||
9 | ------- |
|
|||
10 | * Brian Granger |
|
|||
11 | * Fernando Perez |
|
|||
12 | * Min RK |
|
|||
13 | """ |
|
|||
14 |
|
||||
15 | #----------------------------------------------------------------------------- |
|
|||
16 | # Copyright (C) 2008-2011 The IPython Development Team |
|
|||
17 | # |
|
|||
18 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
19 | # the file COPYING, distributed as part of this software. |
|
|||
20 | #----------------------------------------------------------------------------- |
|
|||
21 |
|
||||
22 | #----------------------------------------------------------------------------- |
|
|||
23 | # Imports |
|
|||
24 | #----------------------------------------------------------------------------- |
|
|||
25 |
|
6 | |||
26 | import argparse |
|
7 | import argparse | |
27 | import copy |
|
8 | import copy | |
@@ -308,11 +289,8 b' class ConfigLoader(object):' | |||||
308 | """ |
|
289 | """ | |
309 |
|
290 | |||
310 | def _log_default(self): |
|
291 | def _log_default(self): | |
311 |
from IPython. |
|
292 | from IPython.utils.log import get_logger | |
312 | if Application.initialized(): |
|
293 | return get_logger() | |
313 | return Application.instance().log |
|
|||
314 | else: |
|
|||
315 | return logging.getLogger() |
|
|||
316 |
|
294 | |||
317 | def __init__(self, log=None): |
|
295 | def __init__(self, log=None): | |
318 | """A base class for config loaders. |
|
296 | """A base class for config loaders. |
@@ -165,8 +165,6 b' class IPythonConsoleApp(ConnectionFileMixin):' | |||||
165 | if argv is None: |
|
165 | if argv is None: | |
166 | argv = sys.argv[1:] |
|
166 | argv = sys.argv[1:] | |
167 | self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags) |
|
167 | self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags) | |
168 | # kernel should inherit default config file from frontend |
|
|||
169 | self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name) |
|
|||
170 |
|
168 | |||
171 | def init_connection_file(self): |
|
169 | def init_connection_file(self): | |
172 | """find the connection file, and load the info if found. |
|
170 | """find the connection file, and load the info if found. | |
@@ -289,6 +287,7 b' class IPythonConsoleApp(ConnectionFileMixin):' | |||||
289 | try: |
|
287 | try: | |
290 | self.kernel_manager = self.kernel_manager_class( |
|
288 | self.kernel_manager = self.kernel_manager_class( | |
291 | ip=self.ip, |
|
289 | ip=self.ip, | |
|
290 | session=self.session, | |||
292 | transport=self.transport, |
|
291 | transport=self.transport, | |
293 | shell_port=self.shell_port, |
|
292 | shell_port=self.shell_port, | |
294 | iopub_port=self.iopub_port, |
|
293 | iopub_port=self.iopub_port, | |
@@ -326,6 +325,7 b' class IPythonConsoleApp(ConnectionFileMixin):' | |||||
326 | self.kernel_client = self.kernel_manager.client() |
|
325 | self.kernel_client = self.kernel_manager.client() | |
327 | else: |
|
326 | else: | |
328 | self.kernel_client = self.kernel_client_class( |
|
327 | self.kernel_client = self.kernel_client_class( | |
|
328 | session=self.session, | |||
329 | ip=self.ip, |
|
329 | ip=self.ip, | |
330 | transport=self.transport, |
|
330 | transport=self.transport, | |
331 | shell_port=self.shell_port, |
|
331 | shell_port=self.shell_port, |
@@ -80,6 +80,7 b' from IPython.core.error import TryNext' | |||||
80 | from IPython.core.inputsplitter import ESC_MAGIC |
|
80 | from IPython.core.inputsplitter import ESC_MAGIC | |
81 | from IPython.utils import generics |
|
81 | from IPython.utils import generics | |
82 | from IPython.utils import io |
|
82 | from IPython.utils import io | |
|
83 | from IPython.utils.decorators import undoc | |||
83 | from IPython.utils.dir2 import dir2 |
|
84 | from IPython.utils.dir2 import dir2 | |
84 | from IPython.utils.process import arg_split |
|
85 | from IPython.utils.process import arg_split | |
85 | from IPython.utils.py3compat import builtin_mod, string_types |
|
86 | from IPython.utils.py3compat import builtin_mod, string_types | |
@@ -216,7 +217,7 b' def penalize_magics_key(word):' | |||||
216 | return word |
|
217 | return word | |
217 |
|
218 | |||
218 |
|
219 | |||
219 |
|
220 | @undoc | ||
220 | class Bunch(object): pass |
|
221 | class Bunch(object): pass | |
221 |
|
222 | |||
222 |
|
223 | |||
@@ -865,6 +866,7 b' class IPCompleter(Completer):' | |||||
865 | return argMatches |
|
866 | return argMatches | |
866 |
|
867 | |||
867 | def dict_key_matches(self, text): |
|
868 | def dict_key_matches(self, text): | |
|
869 | "Match string keys in a dictionary, after e.g. 'foo[' " | |||
868 | def get_keys(obj): |
|
870 | def get_keys(obj): | |
869 | # Only allow completion for known in-memory dict-like types |
|
871 | # Only allow completion for known in-memory dict-like types | |
870 | if isinstance(obj, dict) or\ |
|
872 | if isinstance(obj, dict) or\ | |
@@ -1010,9 +1012,6 b' class IPCompleter(Completer):' | |||||
1010 | def complete(self, text=None, line_buffer=None, cursor_pos=None): |
|
1012 | def complete(self, text=None, line_buffer=None, cursor_pos=None): | |
1011 | """Find completions for the given text and line context. |
|
1013 | """Find completions for the given text and line context. | |
1012 |
|
1014 | |||
1013 | This is called successively with state == 0, 1, 2, ... until it |
|
|||
1014 | returns None. The completion should begin with 'text'. |
|
|||
1015 |
|
||||
1016 | Note that both the text and the line_buffer are optional, but at least |
|
1015 | Note that both the text and the line_buffer are optional, but at least | |
1017 | one of them must be given. |
|
1016 | one of them must be given. | |
1018 |
|
1017 |
@@ -26,7 +26,13 b' from IPython.core.formatters import _safe_get_formatter_method' | |||||
26 | from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode, |
|
26 | from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode, | |
27 | unicode_type) |
|
27 | unicode_type) | |
28 | from IPython.testing.skipdoctest import skip_doctest |
|
28 | from IPython.testing.skipdoctest import skip_doctest | |
29 | from .displaypub import publish_display_data |
|
29 | ||
|
30 | __all__ = ['display', 'display_pretty', 'display_html', 'display_markdown', | |||
|
31 | 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json', | |||
|
32 | 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject', | |||
|
33 | 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'JSON', 'Javascript', | |||
|
34 | 'Image', 'clear_output', 'set_matplotlib_formats', 'set_matplotlib_close', | |||
|
35 | 'publish_display_data'] | |||
30 |
|
36 | |||
31 | #----------------------------------------------------------------------------- |
|
37 | #----------------------------------------------------------------------------- | |
32 | # utility functions |
|
38 | # utility functions | |
@@ -78,6 +84,48 b' def _display_mimetype(mimetype, objs, raw=False, metadata=None):' | |||||
78 | # Main functions |
|
84 | # Main functions | |
79 | #----------------------------------------------------------------------------- |
|
85 | #----------------------------------------------------------------------------- | |
80 |
|
86 | |||
|
87 | def publish_display_data(data, metadata=None, source=None): | |||
|
88 | """Publish data and metadata to all frontends. | |||
|
89 | ||||
|
90 | See the ``display_data`` message in the messaging documentation for | |||
|
91 | more details about this message type. | |||
|
92 | ||||
|
93 | The following MIME types are currently implemented: | |||
|
94 | ||||
|
95 | * text/plain | |||
|
96 | * text/html | |||
|
97 | * text/markdown | |||
|
98 | * text/latex | |||
|
99 | * application/json | |||
|
100 | * application/javascript | |||
|
101 | * image/png | |||
|
102 | * image/jpeg | |||
|
103 | * image/svg+xml | |||
|
104 | ||||
|
105 | Parameters | |||
|
106 | ---------- | |||
|
107 | data : dict | |||
|
108 | A dictionary having keys that are valid MIME types (like | |||
|
109 | 'text/plain' or 'image/svg+xml') and values that are the data for | |||
|
110 | that MIME type. The data itself must be a JSON'able data | |||
|
111 | structure. Minimally all data should have the 'text/plain' data, | |||
|
112 | which can be displayed by all frontends. If more than the plain | |||
|
113 | text is given, it is up to the frontend to decide which | |||
|
114 | representation to use. | |||
|
115 | metadata : dict | |||
|
116 | A dictionary for metadata related to the data. This can contain | |||
|
117 | arbitrary key, value pairs that frontends can use to interpret | |||
|
118 | the data. mime-type keys matching those in data can be used | |||
|
119 | to specify metadata about particular representations. | |||
|
120 | source : str, deprecated | |||
|
121 | Unused. | |||
|
122 | """ | |||
|
123 | from IPython.core.interactiveshell import InteractiveShell | |||
|
124 | InteractiveShell.instance().display_pub.publish( | |||
|
125 | data=data, | |||
|
126 | metadata=metadata, | |||
|
127 | ) | |||
|
128 | ||||
81 | def display(*objs, **kwargs): |
|
129 | def display(*objs, **kwargs): | |
82 | """Display a Python object in all frontends. |
|
130 | """Display a Python object in all frontends. | |
83 |
|
131 |
@@ -19,9 +19,11 b' from __future__ import print_function' | |||||
19 |
|
19 | |||
20 | from IPython.config.configurable import Configurable |
|
20 | from IPython.config.configurable import Configurable | |
21 | from IPython.utils import io |
|
21 | from IPython.utils import io | |
22 | from IPython.utils.py3compat import string_types |
|
|||
23 | from IPython.utils.traitlets import List |
|
22 | from IPython.utils.traitlets import List | |
24 |
|
23 | |||
|
24 | # This used to be defined here - it is imported for backwards compatibility | |||
|
25 | from .display import publish_display_data | |||
|
26 | ||||
25 | #----------------------------------------------------------------------------- |
|
27 | #----------------------------------------------------------------------------- | |
26 | # Main payload class |
|
28 | # Main payload class | |
27 | #----------------------------------------------------------------------------- |
|
29 | #----------------------------------------------------------------------------- | |
@@ -112,48 +114,3 b' class CapturingDisplayPublisher(DisplayPublisher):' | |||||
112 |
|
114 | |||
113 | # empty the list, *do not* reassign a new list |
|
115 | # empty the list, *do not* reassign a new list | |
114 | del self.outputs[:] |
|
116 | del self.outputs[:] | |
115 |
|
||||
116 |
|
||||
117 | def publish_display_data(data, metadata=None, source=None): |
|
|||
118 | """Publish data and metadata to all frontends. |
|
|||
119 |
|
||||
120 | See the ``display_data`` message in the messaging documentation for |
|
|||
121 | more details about this message type. |
|
|||
122 |
|
||||
123 | The following MIME types are currently implemented: |
|
|||
124 |
|
||||
125 | * text/plain |
|
|||
126 | * text/html |
|
|||
127 | * text/markdown |
|
|||
128 | * text/latex |
|
|||
129 | * application/json |
|
|||
130 | * application/javascript |
|
|||
131 | * image/png |
|
|||
132 | * image/jpeg |
|
|||
133 | * image/svg+xml |
|
|||
134 |
|
||||
135 | Parameters |
|
|||
136 | ---------- |
|
|||
137 | data : dict |
|
|||
138 | A dictionary having keys that are valid MIME types (like |
|
|||
139 | 'text/plain' or 'image/svg+xml') and values that are the data for |
|
|||
140 | that MIME type. The data itself must be a JSON'able data |
|
|||
141 | structure. Minimally all data should have the 'text/plain' data, |
|
|||
142 | which can be displayed by all frontends. If more than the plain |
|
|||
143 | text is given, it is up to the frontend to decide which |
|
|||
144 | representation to use. |
|
|||
145 | metadata : dict |
|
|||
146 | A dictionary for metadata related to the data. This can contain |
|
|||
147 | arbitrary key, value pairs that frontends can use to interpret |
|
|||
148 | the data. mime-type keys matching those in data can be used |
|
|||
149 | to specify metadata about particular representations. |
|
|||
150 | source : str, deprecated |
|
|||
151 | Unused. |
|
|||
152 | """ |
|
|||
153 | from IPython.core.interactiveshell import InteractiveShell |
|
|||
154 | InteractiveShell.instance().display_pub.publish( |
|
|||
155 | data=data, |
|
|||
156 | metadata=metadata, |
|
|||
157 | ) |
|
|||
158 |
|
||||
159 |
|
@@ -736,12 +736,13 b' class InteractiveShell(SingletonConfigurable):' | |||||
736 | # stdlib venv may symlink sys.executable, so we can't use realpath. |
|
736 | # stdlib venv may symlink sys.executable, so we can't use realpath. | |
737 | # but others can symlink *to* the venv Python, so we can't just use sys.executable. |
|
737 | # but others can symlink *to* the venv Python, so we can't just use sys.executable. | |
738 | # So we just check every item in the symlink tree (generally <= 3) |
|
738 | # So we just check every item in the symlink tree (generally <= 3) | |
739 | p = sys.executable |
|
739 | p = os.path.normcase(sys.executable) | |
740 | paths = [p] |
|
740 | paths = [p] | |
741 | while os.path.islink(p): |
|
741 | while os.path.islink(p): | |
742 | p = os.path.join(os.path.dirname(p), os.readlink(p)) |
|
742 | p = os.path.normcase(os.path.join(os.path.dirname(p), os.readlink(p))) | |
743 | paths.append(p) |
|
743 | paths.append(p) | |
744 | if any(p.startswith(os.environ['VIRTUAL_ENV']) for p in paths): |
|
744 | p_venv = os.path.normcase(os.environ['VIRTUAL_ENV']) | |
|
745 | if any(p.startswith(p_venv) for p in paths): | |||
745 | # Running properly in the virtualenv, don't need to do anything |
|
746 | # Running properly in the virtualenv, don't need to do anything | |
746 | return |
|
747 | return | |
747 |
|
748 | |||
@@ -910,7 +911,8 b' class InteractiveShell(SingletonConfigurable):' | |||||
910 | try: |
|
911 | try: | |
911 | main_mod = self._main_mod_cache[filename] |
|
912 | main_mod = self._main_mod_cache[filename] | |
912 | except KeyError: |
|
913 | except KeyError: | |
913 |
main_mod = self._main_mod_cache[filename] = types.ModuleType( |
|
914 | main_mod = self._main_mod_cache[filename] = types.ModuleType( | |
|
915 | py3compat.cast_bytes_py2(modname), | |||
914 | doc="Module created for script run in IPython") |
|
916 | doc="Module created for script run in IPython") | |
915 | else: |
|
917 | else: | |
916 | main_mod.__dict__.clear() |
|
918 | main_mod.__dict__.clear() | |
@@ -1735,7 +1737,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
1735 | This hook should be used sparingly, only in places which are not likely |
|
1737 | This hook should be used sparingly, only in places which are not likely | |
1736 | to be true IPython errors. |
|
1738 | to be true IPython errors. | |
1737 | """ |
|
1739 | """ | |
1738 | self.showtraceback((etype,value,tb),tb_offset=0) |
|
1740 | self.showtraceback((etype, value, tb), tb_offset=0) | |
1739 |
|
1741 | |||
1740 | def _get_exc_info(self, exc_tuple=None): |
|
1742 | def _get_exc_info(self, exc_tuple=None): | |
1741 | """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc. |
|
1743 | """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc. | |
@@ -1776,7 +1778,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
1776 | """ |
|
1778 | """ | |
1777 | self.write_err("UsageError: %s" % exc) |
|
1779 | self.write_err("UsageError: %s" % exc) | |
1778 |
|
1780 | |||
1779 |
def showtraceback(self,exc_tuple |
|
1781 | def showtraceback(self, exc_tuple=None, filename=None, tb_offset=None, | |
1780 | exception_only=False): |
|
1782 | exception_only=False): | |
1781 | """Display the exception that just occurred. |
|
1783 | """Display the exception that just occurred. | |
1782 |
|
1784 | |||
@@ -2918,10 +2920,9 b' class InteractiveShell(SingletonConfigurable):' | |||||
2918 | False : successful execution. |
|
2920 | False : successful execution. | |
2919 | True : an error occurred. |
|
2921 | True : an error occurred. | |
2920 | """ |
|
2922 | """ | |
2921 |
|
||||
2922 | # Set our own excepthook in case the user code tries to call it |
|
2923 | # Set our own excepthook in case the user code tries to call it | |
2923 | # directly, so that the IPython crash handler doesn't get triggered |
|
2924 | # directly, so that the IPython crash handler doesn't get triggered | |
2924 | old_excepthook,sys.excepthook = sys.excepthook, self.excepthook |
|
2925 | old_excepthook, sys.excepthook = sys.excepthook, self.excepthook | |
2925 |
|
2926 | |||
2926 | # we save the original sys.excepthook in the instance, in case config |
|
2927 | # we save the original sys.excepthook in the instance, in case config | |
2927 | # code (such as magics) needs access to it. |
|
2928 | # code (such as magics) needs access to it. | |
@@ -2939,8 +2940,8 b' class InteractiveShell(SingletonConfigurable):' | |||||
2939 | self.showtraceback(exception_only=True) |
|
2940 | self.showtraceback(exception_only=True) | |
2940 | warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1) |
|
2941 | warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1) | |
2941 | except self.custom_exceptions: |
|
2942 | except self.custom_exceptions: | |
2942 | etype,value,tb = sys.exc_info() |
|
2943 | etype, value, tb = sys.exc_info() | |
2943 | self.CustomTB(etype,value,tb) |
|
2944 | self.CustomTB(etype, value, tb) | |
2944 | except: |
|
2945 | except: | |
2945 | self.showtraceback() |
|
2946 | self.showtraceback() | |
2946 | else: |
|
2947 | else: | |
@@ -3087,6 +3088,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
3087 | self.tempdirs.append(dirname) |
|
3088 | self.tempdirs.append(dirname) | |
3088 |
|
3089 | |||
3089 | handle, filename = tempfile.mkstemp('.py', prefix, dir=dirname) |
|
3090 | handle, filename = tempfile.mkstemp('.py', prefix, dir=dirname) | |
|
3091 | os.close(handle) # On Windows, there can only be one open handle on a file | |||
3090 | self.tempfiles.append(filename) |
|
3092 | self.tempfiles.append(filename) | |
3091 |
|
3093 | |||
3092 | if data: |
|
3094 | if data: |
@@ -193,6 +193,8 b' class ScriptMagics(Magics):' | |||||
193 | else: |
|
193 | else: | |
194 | raise |
|
194 | raise | |
195 |
|
195 | |||
|
196 | if not cell.endswith('\n'): | |||
|
197 | cell += '\n' | |||
196 | cell = cell.encode('utf8', 'replace') |
|
198 | cell = cell.encode('utf8', 'replace') | |
197 | if args.bg: |
|
199 | if args.bg: | |
198 | self.bg_processes.append(p) |
|
200 | self.bg_processes.append(p) |
@@ -6,6 +6,7 b'' | |||||
6 | #----------------------------------------------------------------------------- |
|
6 | #----------------------------------------------------------------------------- | |
7 |
|
7 | |||
8 | # stdlib |
|
8 | # stdlib | |
|
9 | import io | |||
9 | import os |
|
10 | import os | |
10 | import sys |
|
11 | import sys | |
11 | import tempfile |
|
12 | import tempfile | |
@@ -124,7 +125,7 b' def test_history():' | |||||
124 | # Cross testing: check that magic %save can get previous session. |
|
125 | # Cross testing: check that magic %save can get previous session. | |
125 | testfilename = os.path.realpath(os.path.join(tmpdir, "test.py")) |
|
126 | testfilename = os.path.realpath(os.path.join(tmpdir, "test.py")) | |
126 | ip.magic("save " + testfilename + " ~1/1-3") |
|
127 | ip.magic("save " + testfilename + " ~1/1-3") | |
127 |
with |
|
128 | with io.open(testfilename, encoding='utf-8') as testfile: | |
128 | nt.assert_equal(testfile.read(), |
|
129 | nt.assert_equal(testfile.read(), | |
129 | u"# coding: utf-8\n" + u"\n".join(hist)+u"\n") |
|
130 | u"# coding: utf-8\n" + u"\n".join(hist)+u"\n") | |
130 |
|
131 |
@@ -462,6 +462,21 b' class InteractiveShellTestCase(unittest.TestCase):' | |||||
462 | ip.run_cell("d = 1/2", shell_futures=True) |
|
462 | ip.run_cell("d = 1/2", shell_futures=True) | |
463 | self.assertEqual(ip.user_ns['d'], 0) |
|
463 | self.assertEqual(ip.user_ns['d'], 0) | |
464 |
|
464 | |||
|
465 | def test_mktempfile(self): | |||
|
466 | filename = ip.mktempfile() | |||
|
467 | # Check that we can open the file again on Windows | |||
|
468 | with open(filename, 'w') as f: | |||
|
469 | f.write('abc') | |||
|
470 | ||||
|
471 | filename = ip.mktempfile(data='blah') | |||
|
472 | with open(filename, 'r') as f: | |||
|
473 | self.assertEqual(f.read(), 'blah') | |||
|
474 | ||||
|
475 | def test_new_main_mod(self): | |||
|
476 | # Smoketest to check that this accepts a unicode module name | |||
|
477 | name = u'jiefmw' | |||
|
478 | mod = ip.new_main_mod(u'%s.py' % name, name) | |||
|
479 | self.assertEqual(mod.__name__, name) | |||
465 |
|
480 | |||
466 | class TestSafeExecfileNonAsciiPath(unittest.TestCase): |
|
481 | class TestSafeExecfileNonAsciiPath(unittest.TestCase): | |
467 |
|
482 |
@@ -9,6 +9,7 b' from IPython.testing import tools as tt' | |||||
9 | from IPython.testing.decorators import onlyif_unicode_paths |
|
9 | from IPython.testing.decorators import onlyif_unicode_paths | |
10 | from IPython.utils.syspathcontext import prepended_to_syspath |
|
10 | from IPython.utils.syspathcontext import prepended_to_syspath | |
11 | from IPython.utils.tempdir import TemporaryDirectory |
|
11 | from IPython.utils.tempdir import TemporaryDirectory | |
|
12 | from IPython.utils.py3compat import PY3 | |||
12 |
|
13 | |||
13 | ip = get_ipython() |
|
14 | ip = get_ipython() | |
14 |
|
15 | |||
@@ -147,3 +148,37 b' class SyntaxErrorTest(unittest.TestCase):' | |||||
147 | except ValueError: |
|
148 | except ValueError: | |
148 | with tt.AssertPrints('QWERTY'): |
|
149 | with tt.AssertPrints('QWERTY'): | |
149 | ip.showsyntaxerror() |
|
150 | ip.showsyntaxerror() | |
|
151 | ||||
|
152 | ||||
|
153 | class Python3ChainedExceptionsTest(unittest.TestCase): | |||
|
154 | DIRECT_CAUSE_ERROR_CODE = """ | |||
|
155 | try: | |||
|
156 | x = 1 + 2 | |||
|
157 | print(not_defined_here) | |||
|
158 | except Exception as e: | |||
|
159 | x += 55 | |||
|
160 | x - 1 | |||
|
161 | y = {} | |||
|
162 | raise KeyError('uh') from e | |||
|
163 | """ | |||
|
164 | ||||
|
165 | EXCEPTION_DURING_HANDLING_CODE = """ | |||
|
166 | try: | |||
|
167 | x = 1 + 2 | |||
|
168 | print(not_defined_here) | |||
|
169 | except Exception as e: | |||
|
170 | x += 55 | |||
|
171 | x - 1 | |||
|
172 | y = {} | |||
|
173 | raise KeyError('uh') | |||
|
174 | """ | |||
|
175 | ||||
|
176 | def test_direct_cause_error(self): | |||
|
177 | if PY3: | |||
|
178 | with tt.AssertPrints(["KeyError", "NameError", "direct cause"]): | |||
|
179 | ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE) | |||
|
180 | ||||
|
181 | def test_exception_during_handling_error(self): | |||
|
182 | if PY3: | |||
|
183 | with tt.AssertPrints(["KeyError", "NameError", "During handling"]): | |||
|
184 | ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE) |
This diff has been collapsed as it changes many lines, (533 lines changed) Show them Hide them | |||||
@@ -39,7 +39,7 b" Give it a shot--you'll love it or you'll hate it." | |||||
39 | Verbose). |
|
39 | Verbose). | |
40 |
|
40 | |||
41 |
|
41 | |||
42 |
Installation instructions for |
|
42 | Installation instructions for VerboseTB:: | |
43 |
|
43 | |||
44 | import sys,ultratb |
|
44 | import sys,ultratb | |
45 | sys.excepthook = ultratb.VerboseTB() |
|
45 | sys.excepthook = ultratb.VerboseTB() | |
@@ -73,11 +73,11 b' Inheritance diagram:' | |||||
73 | """ |
|
73 | """ | |
74 |
|
74 | |||
75 | #***************************************************************************** |
|
75 | #***************************************************************************** | |
76 |
# |
|
76 | # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu> | |
77 |
# |
|
77 | # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu> | |
78 | # |
|
78 | # | |
79 |
# |
|
79 | # Distributed under the terms of the BSD License. The full license is in | |
80 |
# |
|
80 | # the file COPYING, distributed as part of this software. | |
81 | #***************************************************************************** |
|
81 | #***************************************************************************** | |
82 |
|
82 | |||
83 | from __future__ import unicode_literals |
|
83 | from __future__ import unicode_literals | |
@@ -95,14 +95,14 b' import tokenize' | |||||
95 | import traceback |
|
95 | import traceback | |
96 | import types |
|
96 | import types | |
97 |
|
97 | |||
98 | try: # Python 2 |
|
98 | try: # Python 2 | |
99 | generate_tokens = tokenize.generate_tokens |
|
99 | generate_tokens = tokenize.generate_tokens | |
100 |
except AttributeError: |
|
100 | except AttributeError: # Python 3 | |
101 | generate_tokens = tokenize.tokenize |
|
101 | generate_tokens = tokenize.tokenize | |
102 |
|
102 | |||
103 | # For purposes of monkeypatching inspect to fix a bug in it. |
|
103 | # For purposes of monkeypatching inspect to fix a bug in it. | |
104 | from inspect import getsourcefile, getfile, getmodule,\ |
|
104 | from inspect import getsourcefile, getfile, getmodule, \ | |
105 |
|
|
105 | ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode | |
106 |
|
106 | |||
107 | # IPython's own modules |
|
107 | # IPython's own modules | |
108 | # Modified pdb which doesn't damage IPython's readline handling |
|
108 | # Modified pdb which doesn't damage IPython's readline handling | |
@@ -125,11 +125,11 b' INDENT_SIZE = 8' | |||||
125 |
|
125 | |||
126 | # Default color scheme. This is used, for example, by the traceback |
|
126 | # Default color scheme. This is used, for example, by the traceback | |
127 | # formatter. When running in an actual IPython instance, the user's rc.colors |
|
127 | # formatter. When running in an actual IPython instance, the user's rc.colors | |
128 | # value is used, but havinga module global makes this functionality available |
|
128 | # value is used, but having a module global makes this functionality available | |
129 | # to users of ultratb who are NOT running inside ipython. |
|
129 | # to users of ultratb who are NOT running inside ipython. | |
130 | DEFAULT_SCHEME = 'NoColor' |
|
130 | DEFAULT_SCHEME = 'NoColor' | |
131 |
|
131 | |||
132 | #--------------------------------------------------------------------------- |
|
132 | # --------------------------------------------------------------------------- | |
133 | # Code begins |
|
133 | # Code begins | |
134 |
|
134 | |||
135 | # Utility functions |
|
135 | # Utility functions | |
@@ -141,6 +141,7 b' def inspect_error():' | |||||
141 | error('Internal Python error in the inspect module.\n' |
|
141 | error('Internal Python error in the inspect module.\n' | |
142 | 'Below is the traceback from this internal error.\n') |
|
142 | 'Below is the traceback from this internal error.\n') | |
143 |
|
143 | |||
|
144 | ||||
144 | # This function is a monkeypatch we apply to the Python inspect module. We have |
|
145 | # This function is a monkeypatch we apply to the Python inspect module. We have | |
145 | # now found when it's needed (see discussion on issue gh-1456), and we have a |
|
146 | # now found when it's needed (see discussion on issue gh-1456), and we have a | |
146 | # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if |
|
147 | # test case (IPython.core.tests.test_ultratb.ChangedPyFileTest) that fails if | |
@@ -212,7 +213,7 b' def findsource(object):' | |||||
212 | pmatch = pat.match |
|
213 | pmatch = pat.match | |
213 | # fperez - fix: sometimes, co_firstlineno can give a number larger than |
|
214 | # fperez - fix: sometimes, co_firstlineno can give a number larger than | |
214 | # the length of lines, which causes an error. Safeguard against that. |
|
215 | # the length of lines, which causes an error. Safeguard against that. | |
215 | lnum = min(object.co_firstlineno,len(lines))-1 |
|
216 | lnum = min(object.co_firstlineno, len(lines)) - 1 | |
216 | while lnum > 0: |
|
217 | while lnum > 0: | |
217 | if pmatch(lines[lnum]): break |
|
218 | if pmatch(lines[lnum]): break | |
218 | lnum -= 1 |
|
219 | lnum -= 1 | |
@@ -220,9 +221,11 b' def findsource(object):' | |||||
220 | return lines, lnum |
|
221 | return lines, lnum | |
221 | raise IOError('could not find code object') |
|
222 | raise IOError('could not find code object') | |
222 |
|
223 | |||
|
224 | ||||
223 | # Monkeypatch inspect to apply our bugfix. |
|
225 | # Monkeypatch inspect to apply our bugfix. | |
224 | def with_patch_inspect(f): |
|
226 | def with_patch_inspect(f): | |
225 | """decorator for monkeypatching inspect.findsource""" |
|
227 | """decorator for monkeypatching inspect.findsource""" | |
|
228 | ||||
226 | def wrapped(*args, **kwargs): |
|
229 | def wrapped(*args, **kwargs): | |
227 | save_findsource = inspect.findsource |
|
230 | save_findsource = inspect.findsource | |
228 | inspect.findsource = findsource |
|
231 | inspect.findsource = findsource | |
@@ -230,8 +233,10 b' def with_patch_inspect(f):' | |||||
230 | return f(*args, **kwargs) |
|
233 | return f(*args, **kwargs) | |
231 | finally: |
|
234 | finally: | |
232 | inspect.findsource = save_findsource |
|
235 | inspect.findsource = save_findsource | |
|
236 | ||||
233 | return wrapped |
|
237 | return wrapped | |
234 |
|
238 | |||
|
239 | ||||
235 | def fix_frame_records_filenames(records): |
|
240 | def fix_frame_records_filenames(records): | |
236 | """Try to fix the filenames in each record from inspect.getinnerframes(). |
|
241 | """Try to fix the filenames in each record from inspect.getinnerframes(). | |
237 |
|
242 | |||
@@ -253,11 +258,10 b' def fix_frame_records_filenames(records):' | |||||
253 |
|
258 | |||
254 |
|
259 | |||
255 | @with_patch_inspect |
|
260 | @with_patch_inspect | |
256 | def _fixed_getinnerframes(etb, context=1,tb_offset=0): |
|
261 | def _fixed_getinnerframes(etb, context=1, tb_offset=0): | |
257 |
LNUM_POS, LINES_POS, INDEX_POS = |
|
262 | LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5 | |
258 |
|
||||
259 | records = fix_frame_records_filenames(inspect.getinnerframes(etb, context)) |
|
|||
260 |
|
263 | |||
|
264 | records = fix_frame_records_filenames(inspect.getinnerframes(etb, context)) | |||
261 | # If the error is at the console, don't build any context, since it would |
|
265 | # If the error is at the console, don't build any context, since it would | |
262 | # otherwise produce 5 blank lines printed out (there is no file at the |
|
266 | # otherwise produce 5 blank lines printed out (there is no file at the | |
263 | # console) |
|
267 | # console) | |
@@ -272,9 +276,9 b' def _fixed_getinnerframes(etb, context=1,tb_offset=0):' | |||||
272 | aux = traceback.extract_tb(etb) |
|
276 | aux = traceback.extract_tb(etb) | |
273 | assert len(records) == len(aux) |
|
277 | assert len(records) == len(aux) | |
274 | for i, (file, lnum, _, _) in zip(range(len(records)), aux): |
|
278 | for i, (file, lnum, _, _) in zip(range(len(records)), aux): | |
275 | maybeStart = lnum-1 - context//2 |
|
279 | maybeStart = lnum - 1 - context // 2 | |
276 |
start = |
|
280 | start = max(maybeStart, 0) | |
277 |
end |
|
281 | end = start + context | |
278 | lines = ulinecache.getlines(file)[start:end] |
|
282 | lines = ulinecache.getlines(file)[start:end] | |
279 | buf = list(records[i]) |
|
283 | buf = list(records[i]) | |
280 | buf[LNUM_POS] = lnum |
|
284 | buf[LNUM_POS] = lnum | |
@@ -290,7 +294,8 b' def _fixed_getinnerframes(etb, context=1,tb_offset=0):' | |||||
290 |
|
294 | |||
291 | _parser = PyColorize.Parser() |
|
295 | _parser = PyColorize.Parser() | |
292 |
|
296 | |||
293 | def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None): |
|
297 | ||
|
298 | def _format_traceback_lines(lnum, index, lines, Colors, lvals=None, scheme=None): | |||
294 | numbers_width = INDENT_SIZE - 1 |
|
299 | numbers_width = INDENT_SIZE - 1 | |
295 | res = [] |
|
300 | res = [] | |
296 | i = lnum - index |
|
301 | i = lnum - index | |
@@ -315,7 +320,7 b' def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):' | |||||
315 | # This is the line with the error |
|
320 | # This is the line with the error | |
316 | pad = numbers_width - len(str(i)) |
|
321 | pad = numbers_width - len(str(i)) | |
317 | if pad >= 3: |
|
322 | if pad >= 3: | |
318 | marker = '-'*(pad-3) + '-> ' |
|
323 | marker = '-' * (pad - 3) + '-> ' | |
319 | elif pad == 2: |
|
324 | elif pad == 2: | |
320 | marker = '> ' |
|
325 | marker = '> ' | |
321 | elif pad == 1: |
|
326 | elif pad == 1: | |
@@ -323,12 +328,12 b' def _format_traceback_lines(lnum, index, lines, Colors, lvals=None,scheme=None):' | |||||
323 | else: |
|
328 | else: | |
324 | marker = '' |
|
329 | marker = '' | |
325 | num = marker + str(i) |
|
330 | num = marker + str(i) | |
326 | line = '%s%s%s %s%s' %(Colors.linenoEm, num, |
|
331 | line = '%s%s%s %s%s' % (Colors.linenoEm, num, | |
327 | Colors.line, line, Colors.Normal) |
|
332 | Colors.line, line, Colors.Normal) | |
328 | else: |
|
333 | else: | |
329 | num = '%*s' % (numbers_width,i) |
|
334 | num = '%*s' % (numbers_width, i) | |
330 | line = '%s%s%s %s' %(Colors.lineno, num, |
|
335 | line = '%s%s%s %s' % (Colors.lineno, num, | |
331 | Colors.Normal, line) |
|
336 | Colors.Normal, line) | |
332 |
|
337 | |||
333 | res.append(line) |
|
338 | res.append(line) | |
334 | if lvals and i == lnum: |
|
339 | if lvals and i == lnum: | |
@@ -389,16 +394,16 b' class TBTools(object):' | |||||
389 |
|
394 | |||
390 | ostream = property(_get_ostream, _set_ostream) |
|
395 | ostream = property(_get_ostream, _set_ostream) | |
391 |
|
396 | |||
392 | def set_colors(self,*args,**kw): |
|
397 | def set_colors(self, *args, **kw): | |
393 | """Shorthand access to the color table scheme selector method.""" |
|
398 | """Shorthand access to the color table scheme selector method.""" | |
394 |
|
399 | |||
395 | # Set own color table |
|
400 | # Set own color table | |
396 | self.color_scheme_table.set_active_scheme(*args,**kw) |
|
401 | self.color_scheme_table.set_active_scheme(*args, **kw) | |
397 | # for convenience, set Colors to the active scheme |
|
402 | # for convenience, set Colors to the active scheme | |
398 | self.Colors = self.color_scheme_table.active_colors |
|
403 | self.Colors = self.color_scheme_table.active_colors | |
399 | # Also set colors of debugger |
|
404 | # Also set colors of debugger | |
400 | if hasattr(self,'pdb') and self.pdb is not None: |
|
405 | if hasattr(self, 'pdb') and self.pdb is not None: | |
401 | self.pdb.set_colors(*args,**kw) |
|
406 | self.pdb.set_colors(*args, **kw) | |
402 |
|
407 | |||
403 | def color_toggle(self): |
|
408 | def color_toggle(self): | |
404 | """Toggle between the currently active color scheme and NoColor.""" |
|
409 | """Toggle between the currently active color scheme and NoColor.""" | |
@@ -453,7 +458,7 b' class ListTB(TBTools):' | |||||
453 | Because they are meant to be called without a full traceback (only a |
|
458 | Because they are meant to be called without a full traceback (only a | |
454 | list), instances of this class can't call the interactive pdb debugger.""" |
|
459 | list), instances of this class can't call the interactive pdb debugger.""" | |
455 |
|
460 | |||
456 |
def __init__(self,color_scheme |
|
461 | def __init__(self, color_scheme='NoColor', call_pdb=False, ostream=None): | |
457 | TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, |
|
462 | TBTools.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, | |
458 | ostream=ostream) |
|
463 | ostream=ostream) | |
459 |
|
464 | |||
@@ -497,7 +502,7 b' class ListTB(TBTools):' | |||||
497 | elist = elist[tb_offset:] |
|
502 | elist = elist[tb_offset:] | |
498 |
|
503 | |||
499 | out_list.append('Traceback %s(most recent call last)%s:' % |
|
504 | out_list.append('Traceback %s(most recent call last)%s:' % | |
500 |
|
|
505 | (Colors.normalEm, Colors.Normal) + '\n') | |
501 | out_list.extend(self._format_list(elist)) |
|
506 | out_list.extend(self._format_list(elist)) | |
502 | # The exception info should be a single entry in the list. |
|
507 | # The exception info should be a single entry in the list. | |
503 | lines = ''.join(self._format_exception_only(etype, value)) |
|
508 | lines = ''.join(self._format_exception_only(etype, value)) | |
@@ -510,7 +515,7 b' class ListTB(TBTools):' | |||||
510 | ## out_list.append(lines[-1]) |
|
515 | ## out_list.append(lines[-1]) | |
511 |
|
516 | |||
512 | # This means it was indenting everything but the last line by a little |
|
517 | # This means it was indenting everything but the last line by a little | |
513 | # bit. I've disabled this for now, but if we see ugliness somewhre we |
|
518 | # bit. I've disabled this for now, but if we see ugliness somewhere we | |
514 | # can restore it. |
|
519 | # can restore it. | |
515 |
|
520 | |||
516 | return out_list |
|
521 | return out_list | |
@@ -532,25 +537,24 b' class ListTB(TBTools):' | |||||
532 | list = [] |
|
537 | list = [] | |
533 | for filename, lineno, name, line in extracted_list[:-1]: |
|
538 | for filename, lineno, name, line in extracted_list[:-1]: | |
534 | item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \ |
|
539 | item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \ | |
535 |
|
|
540 | (Colors.filename, filename, Colors.Normal, | |
536 |
|
|
541 | Colors.lineno, lineno, Colors.Normal, | |
537 |
|
|
542 | Colors.name, name, Colors.Normal) | |
538 | if line: |
|
543 | if line: | |
539 | item += ' %s\n' % line.strip() |
|
544 | item += ' %s\n' % line.strip() | |
540 | list.append(item) |
|
545 | list.append(item) | |
541 | # Emphasize the last entry |
|
546 | # Emphasize the last entry | |
542 | filename, lineno, name, line = extracted_list[-1] |
|
547 | filename, lineno, name, line = extracted_list[-1] | |
543 | item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \ |
|
548 | item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \ | |
544 |
|
|
549 | (Colors.normalEm, | |
545 |
|
|
550 | Colors.filenameEm, filename, Colors.normalEm, | |
546 |
|
|
551 | Colors.linenoEm, lineno, Colors.normalEm, | |
547 |
|
|
552 | Colors.nameEm, name, Colors.normalEm, | |
548 |
|
|
553 | Colors.Normal) | |
549 | if line: |
|
554 | if line: | |
550 | item += '%s %s%s\n' % (Colors.line, line.strip(), |
|
555 | item += '%s %s%s\n' % (Colors.line, line.strip(), | |
551 |
|
|
556 | Colors.Normal) | |
552 | list.append(item) |
|
557 | list.append(item) | |
553 | #from pprint import pformat; print 'LISTTB', pformat(list) # dbg |
|
|||
554 | return list |
|
558 | return list | |
555 |
|
559 | |||
556 | def _format_exception_only(self, etype, value): |
|
560 | def _format_exception_only(self, etype, value): | |
@@ -572,11 +576,10 b' class ListTB(TBTools):' | |||||
572 | stype = Colors.excName + etype.__name__ + Colors.Normal |
|
576 | stype = Colors.excName + etype.__name__ + Colors.Normal | |
573 | if value is None: |
|
577 | if value is None: | |
574 | # Not sure if this can still happen in Python 2.6 and above |
|
578 | # Not sure if this can still happen in Python 2.6 and above | |
575 |
list.append( |
|
579 | list.append(py3compat.cast_unicode(stype) + '\n') | |
576 | else: |
|
580 | else: | |
577 | if issubclass(etype, SyntaxError): |
|
581 | if issubclass(etype, SyntaxError): | |
578 | have_filedata = True |
|
582 | have_filedata = True | |
579 | #print 'filename is',filename # dbg |
|
|||
580 | if not value.filename: value.filename = "<string>" |
|
583 | if not value.filename: value.filename = "<string>" | |
581 | if value.lineno: |
|
584 | if value.lineno: | |
582 | lineno = value.lineno |
|
585 | lineno = value.lineno | |
@@ -585,9 +588,9 b' class ListTB(TBTools):' | |||||
585 | lineno = 'unknown' |
|
588 | lineno = 'unknown' | |
586 | textline = '' |
|
589 | textline = '' | |
587 | list.append('%s File %s"%s"%s, line %s%s%s\n' % \ |
|
590 | list.append('%s File %s"%s"%s, line %s%s%s\n' % \ | |
588 | (Colors.normalEm, |
|
591 | (Colors.normalEm, | |
589 | Colors.filenameEm, py3compat.cast_unicode(value.filename), Colors.normalEm, |
|
592 | Colors.filenameEm, py3compat.cast_unicode(value.filename), Colors.normalEm, | |
590 | Colors.linenoEm, lineno, Colors.Normal )) |
|
593 | Colors.linenoEm, lineno, Colors.Normal )) | |
591 | if textline == '': |
|
594 | if textline == '': | |
592 | textline = py3compat.cast_unicode(value.text, "utf-8") |
|
595 | textline = py3compat.cast_unicode(value.text, "utf-8") | |
593 |
|
596 | |||
@@ -600,13 +603,13 b' class ListTB(TBTools):' | |||||
600 | Colors.Normal)) |
|
603 | Colors.Normal)) | |
601 | if value.offset is not None: |
|
604 | if value.offset is not None: | |
602 | s = ' ' |
|
605 | s = ' ' | |
603 | for c in textline[i:value.offset-1]: |
|
606 | for c in textline[i:value.offset - 1]: | |
604 | if c.isspace(): |
|
607 | if c.isspace(): | |
605 | s += c |
|
608 | s += c | |
606 | else: |
|
609 | else: | |
607 | s += ' ' |
|
610 | s += ' ' | |
608 | list.append('%s%s^%s\n' % (Colors.caret, s, |
|
611 | list.append('%s%s^%s\n' % (Colors.caret, s, | |
609 |
Colors.Normal) |
|
612 | Colors.Normal)) | |
610 |
|
613 | |||
611 | try: |
|
614 | try: | |
612 | s = value.msg |
|
615 | s = value.msg | |
@@ -636,7 +639,6 b' class ListTB(TBTools):' | |||||
636 | """ |
|
639 | """ | |
637 | return ListTB.structured_traceback(self, etype, value, []) |
|
640 | return ListTB.structured_traceback(self, etype, value, []) | |
638 |
|
641 | |||
639 |
|
||||
640 | def show_exception_only(self, etype, evalue): |
|
642 | def show_exception_only(self, etype, evalue): | |
641 | """Only print the exception type and message, without a traceback. |
|
643 | """Only print the exception type and message, without a traceback. | |
642 |
|
644 | |||
@@ -659,6 +661,7 b' class ListTB(TBTools):' | |||||
659 | except: |
|
661 | except: | |
660 | return '<unprintable %s object>' % type(value).__name__ |
|
662 | return '<unprintable %s object>' % type(value).__name__ | |
661 |
|
663 | |||
|
664 | ||||
662 | #---------------------------------------------------------------------------- |
|
665 | #---------------------------------------------------------------------------- | |
663 | class VerboseTB(TBTools): |
|
666 | class VerboseTB(TBTools): | |
664 | """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead |
|
667 | """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead | |
@@ -668,7 +671,7 b' class VerboseTB(TBTools):' | |||||
668 | traceback, to be used with alternate interpreters (because their own code |
|
671 | traceback, to be used with alternate interpreters (because their own code | |
669 | would appear in the traceback).""" |
|
672 | would appear in the traceback).""" | |
670 |
|
673 | |||
671 |
def __init__(self,color_scheme |
|
674 | def __init__(self, color_scheme='Linux', call_pdb=False, ostream=None, | |
672 | tb_offset=0, long_header=False, include_vars=True, |
|
675 | tb_offset=0, long_header=False, include_vars=True, | |
673 | check_cache=None): |
|
676 | check_cache=None): | |
674 | """Specify traceback offset, headers and color scheme. |
|
677 | """Specify traceback offset, headers and color scheme. | |
@@ -691,126 +694,37 b' class VerboseTB(TBTools):' | |||||
691 | check_cache = linecache.checkcache |
|
694 | check_cache = linecache.checkcache | |
692 | self.check_cache = check_cache |
|
695 | self.check_cache = check_cache | |
693 |
|
696 | |||
694 | def structured_traceback(self, etype, evalue, etb, tb_offset=None, |
|
697 | def format_records(self, records): | |
695 | context=5): |
|
698 | Colors = self.Colors # just a shorthand + quicker name lookup | |
696 | """Return a nice text document describing the traceback.""" |
|
699 | ColorsNormal = Colors.Normal # used a lot | |
697 |
|
700 | col_scheme = self.color_scheme_table.active_scheme_name | ||
698 | tb_offset = self.tb_offset if tb_offset is None else tb_offset |
|
701 | indent = ' ' * INDENT_SIZE | |
699 |
|
702 | em_normal = '%s\n%s%s' % (Colors.valEm, indent, ColorsNormal) | ||
700 | # some locals |
|
703 | undefined = '%sundefined%s' % (Colors.em, ColorsNormal) | |
701 | try: |
|
|||
702 | etype = etype.__name__ |
|
|||
703 | except AttributeError: |
|
|||
704 | pass |
|
|||
705 | Colors = self.Colors # just a shorthand + quicker name lookup |
|
|||
706 | ColorsNormal = Colors.Normal # used a lot |
|
|||
707 | col_scheme = self.color_scheme_table.active_scheme_name |
|
|||
708 | indent = ' '*INDENT_SIZE |
|
|||
709 | em_normal = '%s\n%s%s' % (Colors.valEm, indent,ColorsNormal) |
|
|||
710 | undefined = '%sundefined%s' % (Colors.em, ColorsNormal) |
|
|||
711 | exc = '%s%s%s' % (Colors.excName,etype,ColorsNormal) |
|
|||
712 |
|
||||
713 | # some internal-use functions |
|
|||
714 | def text_repr(value): |
|
|||
715 | """Hopefully pretty robust repr equivalent.""" |
|
|||
716 | # this is pretty horrible but should always return *something* |
|
|||
717 | try: |
|
|||
718 | return pydoc.text.repr(value) |
|
|||
719 | except KeyboardInterrupt: |
|
|||
720 | raise |
|
|||
721 | except: |
|
|||
722 | try: |
|
|||
723 | return repr(value) |
|
|||
724 | except KeyboardInterrupt: |
|
|||
725 | raise |
|
|||
726 | except: |
|
|||
727 | try: |
|
|||
728 | # all still in an except block so we catch |
|
|||
729 | # getattr raising |
|
|||
730 | name = getattr(value, '__name__', None) |
|
|||
731 | if name: |
|
|||
732 | # ick, recursion |
|
|||
733 | return text_repr(name) |
|
|||
734 | klass = getattr(value, '__class__', None) |
|
|||
735 | if klass: |
|
|||
736 | return '%s instance' % text_repr(klass) |
|
|||
737 | except KeyboardInterrupt: |
|
|||
738 | raise |
|
|||
739 | except: |
|
|||
740 | return 'UNRECOVERABLE REPR FAILURE' |
|
|||
741 | def eqrepr(value, repr=text_repr): return '=%s' % repr(value) |
|
|||
742 | def nullrepr(value, repr=text_repr): return '' |
|
|||
743 |
|
||||
744 | # meat of the code begins |
|
|||
745 | try: |
|
|||
746 | etype = etype.__name__ |
|
|||
747 | except AttributeError: |
|
|||
748 | pass |
|
|||
749 |
|
||||
750 | if self.long_header: |
|
|||
751 | # Header with the exception type, python version, and date |
|
|||
752 | pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable |
|
|||
753 | date = time.ctime(time.time()) |
|
|||
754 |
|
||||
755 | head = '%s%s%s\n%s%s%s\n%s' % (Colors.topline, '-'*75, ColorsNormal, |
|
|||
756 | exc, ' '*(75-len(str(etype))-len(pyver)), |
|
|||
757 | pyver, date.rjust(75) ) |
|
|||
758 | head += "\nA problem occured executing Python code. Here is the sequence of function"\ |
|
|||
759 | "\ncalls leading up to the error, with the most recent (innermost) call last." |
|
|||
760 | else: |
|
|||
761 | # Simplified header |
|
|||
762 | head = '%s%s%s\n%s%s' % (Colors.topline, '-'*75, ColorsNormal,exc, |
|
|||
763 | 'Traceback (most recent call last)'.\ |
|
|||
764 | rjust(75 - len(str(etype)) ) ) |
|
|||
765 | frames = [] |
|
704 | frames = [] | |
766 | # Flush cache before calling inspect. This helps alleviate some of the |
|
|||
767 | # problems with python 2.3's inspect.py. |
|
|||
768 | ##self.check_cache() |
|
|||
769 | # Drop topmost frames if requested |
|
|||
770 | try: |
|
|||
771 | # Try the default getinnerframes and Alex's: Alex's fixes some |
|
|||
772 | # problems, but it generates empty tracebacks for console errors |
|
|||
773 | # (5 blanks lines) where none should be returned. |
|
|||
774 | #records = inspect.getinnerframes(etb, context)[tb_offset:] |
|
|||
775 | #print 'python records:', records # dbg |
|
|||
776 | records = _fixed_getinnerframes(etb, context, tb_offset) |
|
|||
777 | #print 'alex records:', records # dbg |
|
|||
778 | except: |
|
|||
779 |
|
||||
780 | # FIXME: I've been getting many crash reports from python 2.3 |
|
|||
781 | # users, traceable to inspect.py. If I can find a small test-case |
|
|||
782 | # to reproduce this, I should either write a better workaround or |
|
|||
783 | # file a bug report against inspect (if that's the real problem). |
|
|||
784 | # So far, I haven't been able to find an isolated example to |
|
|||
785 | # reproduce the problem. |
|
|||
786 | inspect_error() |
|
|||
787 | traceback.print_exc(file=self.ostream) |
|
|||
788 | info('\nUnfortunately, your original traceback can not be constructed.\n') |
|
|||
789 | return '' |
|
|||
790 |
|
||||
791 | # build some color string templates outside these nested loops |
|
705 | # build some color string templates outside these nested loops | |
792 |
tpl_link |
|
706 | tpl_link = '%s%%s%s' % (Colors.filenameEm, ColorsNormal) | |
793 |
tpl_call |
|
707 | tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm, | |
794 |
|
|
708 | ColorsNormal) | |
795 |
tpl_call_fail |
|
709 | tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \ | |
796 |
|
|
710 | (Colors.vName, Colors.valEm, ColorsNormal) | |
797 |
tpl_local_var |
|
711 | tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal) | |
798 | tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal, |
|
712 | tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal, | |
799 | Colors.vName, ColorsNormal) |
|
713 | Colors.vName, ColorsNormal) | |
800 |
tpl_name_val |
|
714 | tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal) | |
801 | tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal) |
|
715 | ||
802 |
tpl_line |
|
716 | tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal) | |
803 | ColorsNormal) |
|
717 | tpl_line_em = '%s%%s%s %%s%s' % (Colors.linenoEm, Colors.line, | |
|
718 | ColorsNormal) | |||
804 |
|
719 | |||
805 | # now, loop over all records printing context and info |
|
|||
806 | abspath = os.path.abspath |
|
720 | abspath = os.path.abspath | |
807 | for frame, file, lnum, func, lines, index in records: |
|
721 | for frame, file, lnum, func, lines, index in records: | |
808 | #print '*** record:',file,lnum,func,lines,index # dbg |
|
722 | #print '*** record:',file,lnum,func,lines,index # dbg | |
809 | if not file: |
|
723 | if not file: | |
810 | file = '?' |
|
724 | file = '?' | |
811 | elif not(file.startswith(str("<")) and file.endswith(str(">"))): |
|
725 | elif not (file.startswith(str("<")) and file.endswith(str(">"))): | |
812 | # Guess that filenames like <string> aren't real filenames, so |
|
726 | # Guess that filenames like <string> aren't real filenames, so | |
813 |
# don't call abspath on them. |
|
727 | # don't call abspath on them. | |
814 | try: |
|
728 | try: | |
815 | file = abspath(file) |
|
729 | file = abspath(file) | |
816 | except OSError: |
|
730 | except OSError: | |
@@ -827,9 +741,9 b' class VerboseTB(TBTools):' | |||||
827 | # Decide whether to include variable details or not |
|
741 | # Decide whether to include variable details or not | |
828 | var_repr = self.include_vars and eqrepr or nullrepr |
|
742 | var_repr = self.include_vars and eqrepr or nullrepr | |
829 | try: |
|
743 | try: | |
830 | call = tpl_call % (func,inspect.formatargvalues(args, |
|
744 | call = tpl_call % (func, inspect.formatargvalues(args, | |
831 | varargs, varkw, |
|
745 | varargs, varkw, | |
832 | locals,formatvalue=var_repr)) |
|
746 | locals, formatvalue=var_repr)) | |
833 | except KeyError: |
|
747 | except KeyError: | |
834 | # This happens in situations like errors inside generator |
|
748 | # This happens in situations like errors inside generator | |
835 | # expressions, where local variables are listed in the |
|
749 | # expressions, where local variables are listed in the | |
@@ -848,12 +762,12 b' class VerboseTB(TBTools):' | |||||
848 | # will illustrate the error, if this exception catch is |
|
762 | # will illustrate the error, if this exception catch is | |
849 | # disabled. |
|
763 | # disabled. | |
850 | call = tpl_call_fail % func |
|
764 | call = tpl_call_fail % func | |
851 |
|
765 | |||
852 | # Don't attempt to tokenize binary files. |
|
766 | # Don't attempt to tokenize binary files. | |
853 | if file.endswith(('.so', '.pyd', '.dll')): |
|
767 | if file.endswith(('.so', '.pyd', '.dll')): | |
854 | frames.append('%s %s\n' % (link,call)) |
|
768 | frames.append('%s %s\n' % (link, call)) | |
855 | continue |
|
769 | continue | |
856 | elif file.endswith(('.pyc','.pyo')): |
|
770 | elif file.endswith(('.pyc', '.pyo')): | |
857 | # Look up the corresponding source file. |
|
771 | # Look up the corresponding source file. | |
858 | file = openpy.source_from_cache(file) |
|
772 | file = openpy.source_from_cache(file) | |
859 |
|
773 | |||
@@ -867,7 +781,7 b' class VerboseTB(TBTools):' | |||||
867 | try: |
|
781 | try: | |
868 | names = [] |
|
782 | names = [] | |
869 | name_cont = False |
|
783 | name_cont = False | |
870 |
|
784 | |||
871 | for token_type, token, start, end, line in generate_tokens(linereader): |
|
785 | for token_type, token, start, end, line in generate_tokens(linereader): | |
872 | # build composite names |
|
786 | # build composite names | |
873 | if token_type == tokenize.NAME and token not in keyword.kwlist: |
|
787 | if token_type == tokenize.NAME and token not in keyword.kwlist: | |
@@ -890,9 +804,11 b' class VerboseTB(TBTools):' | |||||
890 | name_cont = True |
|
804 | name_cont = True | |
891 | elif token_type == tokenize.NEWLINE: |
|
805 | elif token_type == tokenize.NEWLINE: | |
892 | break |
|
806 | break | |
893 |
|
807 | |||
894 | except (IndexError, UnicodeDecodeError): |
|
808 | except (IndexError, UnicodeDecodeError, SyntaxError): | |
895 | # signals exit of tokenizer |
|
809 | # signals exit of tokenizer | |
|
810 | # SyntaxError can occur if the file is not actually Python | |||
|
811 | # - see gh-6300 | |||
896 | pass |
|
812 | pass | |
897 | except tokenize.TokenError as msg: |
|
813 | except tokenize.TokenError as msg: | |
898 | _m = ("An unexpected error occurred while tokenizing input\n" |
|
814 | _m = ("An unexpected error occurred while tokenizing input\n" | |
@@ -909,11 +825,11 b' class VerboseTB(TBTools):' | |||||
909 | lvals = [] |
|
825 | lvals = [] | |
910 | if self.include_vars: |
|
826 | if self.include_vars: | |
911 | for name_full in unique_names: |
|
827 | for name_full in unique_names: | |
912 | name_base = name_full.split('.',1)[0] |
|
828 | name_base = name_full.split('.', 1)[0] | |
913 | if name_base in frame.f_code.co_varnames: |
|
829 | if name_base in frame.f_code.co_varnames: | |
914 | if name_base in locals: |
|
830 | if name_base in locals: | |
915 | try: |
|
831 | try: | |
916 | value = repr(eval(name_full,locals)) |
|
832 | value = repr(eval(name_full, locals)) | |
917 | except: |
|
833 | except: | |
918 | value = undefined |
|
834 | value = undefined | |
919 | else: |
|
835 | else: | |
@@ -922,69 +838,191 b' class VerboseTB(TBTools):' | |||||
922 | else: |
|
838 | else: | |
923 | if name_base in frame.f_globals: |
|
839 | if name_base in frame.f_globals: | |
924 | try: |
|
840 | try: | |
925 | value = repr(eval(name_full,frame.f_globals)) |
|
841 | value = repr(eval(name_full, frame.f_globals)) | |
926 | except: |
|
842 | except: | |
927 | value = undefined |
|
843 | value = undefined | |
928 | else: |
|
844 | else: | |
929 | value = undefined |
|
845 | value = undefined | |
930 | name = tpl_global_var % name_full |
|
846 | name = tpl_global_var % name_full | |
931 | lvals.append(tpl_name_val % (name,value)) |
|
847 | lvals.append(tpl_name_val % (name, value)) | |
932 | if lvals: |
|
848 | if lvals: | |
933 | lvals = '%s%s' % (indent,em_normal.join(lvals)) |
|
849 | lvals = '%s%s' % (indent, em_normal.join(lvals)) | |
934 | else: |
|
850 | else: | |
935 | lvals = '' |
|
851 | lvals = '' | |
936 |
|
852 | |||
937 | level = '%s %s\n' % (link,call) |
|
853 | level = '%s %s\n' % (link, call) | |
938 |
|
854 | |||
939 | if index is None: |
|
855 | if index is None: | |
940 | frames.append(level) |
|
856 | frames.append(level) | |
941 | else: |
|
857 | else: | |
942 | frames.append('%s%s' % (level,''.join( |
|
858 | frames.append('%s%s' % (level, ''.join( | |
943 | _format_traceback_lines(lnum,index,lines,Colors,lvals, |
|
859 | _format_traceback_lines(lnum, index, lines, Colors, lvals, | |
944 | col_scheme)))) |
|
860 | col_scheme)))) | |
945 |
|
861 | |||
|
862 | return frames | |||
|
863 | ||||
|
864 | def prepare_chained_exception_message(self, cause): | |||
|
865 | direct_cause = "\nThe above exception was the direct cause of the following exception:\n" | |||
|
866 | exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n" | |||
|
867 | ||||
|
868 | if cause: | |||
|
869 | message = [[direct_cause]] | |||
|
870 | else: | |||
|
871 | message = [[exception_during_handling]] | |||
|
872 | return message | |||
|
873 | ||||
|
874 | def prepare_header(self, etype, long_version=False): | |||
|
875 | colors = self.Colors # just a shorthand + quicker name lookup | |||
|
876 | colorsnormal = colors.Normal # used a lot | |||
|
877 | exc = '%s%s%s' % (colors.excName, etype, colorsnormal) | |||
|
878 | if long_version: | |||
|
879 | # Header with the exception type, python version, and date | |||
|
880 | pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable | |||
|
881 | date = time.ctime(time.time()) | |||
|
882 | ||||
|
883 | head = '%s%s%s\n%s%s%s\n%s' % (colors.topline, '-' * 75, colorsnormal, | |||
|
884 | exc, ' ' * (75 - len(str(etype)) - len(pyver)), | |||
|
885 | pyver, date.rjust(75) ) | |||
|
886 | head += "\nA problem occurred executing Python code. Here is the sequence of function" \ | |||
|
887 | "\ncalls leading up to the error, with the most recent (innermost) call last." | |||
|
888 | else: | |||
|
889 | # Simplified header | |||
|
890 | head = '%s%s' % (exc, 'Traceback (most recent call last)'. \ | |||
|
891 | rjust(75 - len(str(etype))) ) | |||
|
892 | ||||
|
893 | return head | |||
|
894 | ||||
|
895 | def format_exception(self, etype, evalue): | |||
|
896 | colors = self.Colors # just a shorthand + quicker name lookup | |||
|
897 | colorsnormal = colors.Normal # used a lot | |||
|
898 | indent = ' ' * INDENT_SIZE | |||
946 | # Get (safely) a string form of the exception info |
|
899 | # Get (safely) a string form of the exception info | |
947 | try: |
|
900 | try: | |
948 | etype_str,evalue_str = map(str,(etype,evalue)) |
|
901 | etype_str, evalue_str = map(str, (etype, evalue)) | |
949 | except: |
|
902 | except: | |
950 | # User exception is improperly defined. |
|
903 | # User exception is improperly defined. | |
951 | etype,evalue = str,sys.exc_info()[:2] |
|
904 | etype, evalue = str, sys.exc_info()[:2] | |
952 | etype_str,evalue_str = map(str,(etype,evalue)) |
|
905 | etype_str, evalue_str = map(str, (etype, evalue)) | |
953 | # ... and format it |
|
906 | # ... and format it | |
954 |
exception = ['%s%s%s: %s' % ( |
|
907 | exception = ['%s%s%s: %s' % (colors.excName, etype_str, | |
955 |
|
|
908 | colorsnormal, py3compat.cast_unicode(evalue_str))] | |
|
909 | ||||
956 | if (not py3compat.PY3) and type(evalue) is types.InstanceType: |
|
910 | if (not py3compat.PY3) and type(evalue) is types.InstanceType: | |
957 | try: |
|
911 | try: | |
958 | names = [w for w in dir(evalue) if isinstance(w, py3compat.string_types)] |
|
912 | names = [w for w in dir(evalue) if isinstance(w, py3compat.string_types)] | |
959 | except: |
|
913 | except: | |
960 | # Every now and then, an object with funny inernals blows up |
|
914 | # Every now and then, an object with funny internals blows up | |
961 | # when dir() is called on it. We do the best we can to report |
|
915 | # when dir() is called on it. We do the best we can to report | |
962 | # the problem and continue |
|
916 | # the problem and continue | |
963 | _m = '%sException reporting error (object with broken dir())%s:' |
|
917 | _m = '%sException reporting error (object with broken dir())%s:' | |
964 |
exception.append(_m % ( |
|
918 | exception.append(_m % (colors.excName, colorsnormal)) | |
965 | etype_str,evalue_str = map(str,sys.exc_info()[:2]) |
|
919 | etype_str, evalue_str = map(str, sys.exc_info()[:2]) | |
966 |
exception.append('%s%s%s: %s' % ( |
|
920 | exception.append('%s%s%s: %s' % (colors.excName, etype_str, | |
967 |
|
|
921 | colorsnormal, py3compat.cast_unicode(evalue_str))) | |
968 | names = [] |
|
922 | names = [] | |
969 | for name in names: |
|
923 | for name in names: | |
970 | value = text_repr(getattr(evalue, name)) |
|
924 | value = text_repr(getattr(evalue, name)) | |
971 | exception.append('\n%s%s = %s' % (indent, name, value)) |
|
925 | exception.append('\n%s%s = %s' % (indent, name, value)) | |
972 |
|
926 | |||
973 | # vds: >> |
|
927 | return exception | |
|
928 | ||||
|
929 | def format_exception_as_a_whole(self, etype, evalue, etb, number_of_lines_of_context, tb_offset): | |||
|
930 | # some locals | |||
|
931 | try: | |||
|
932 | etype = etype.__name__ | |||
|
933 | except AttributeError: | |||
|
934 | pass | |||
|
935 | ||||
|
936 | tb_offset = self.tb_offset if tb_offset is None else tb_offset | |||
|
937 | head = self.prepare_header(etype, self.long_header) | |||
|
938 | records = self.get_records(etb, number_of_lines_of_context, tb_offset) | |||
|
939 | ||||
|
940 | frames = self.format_records(records) | |||
|
941 | if records is None: | |||
|
942 | return "" | |||
|
943 | ||||
|
944 | formatted_exception = self.format_exception(etype, evalue) | |||
974 | if records: |
|
945 | if records: | |
975 |
|
|
946 | filepath, lnum = records[-1][1:3] | |
976 | #print "file:", str(file), "linenb", str(lnum) # dbg |
|
947 | filepath = os.path.abspath(filepath) | |
977 | filepath = os.path.abspath(filepath) |
|
948 | ipinst = get_ipython() | |
978 |
ipinst |
|
949 | if ipinst is not None: | |
979 | if ipinst is not None: |
|
950 | ipinst.hooks.synchronize_with_editor(filepath, lnum, 0) | |
980 | ipinst.hooks.synchronize_with_editor(filepath, lnum, 0) |
|
951 | ||
981 | # vds: << |
|
952 | return [[head] + frames + [''.join(formatted_exception[0])]] | |
982 |
|
953 | |||
983 | # return all our info assembled as a single string |
|
954 | def get_records(self, etb, number_of_lines_of_context, tb_offset): | |
984 | # return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) ) |
|
955 | try: | |
985 | return [head] + frames + [''.join(exception[0])] |
|
956 | # Try the default getinnerframes and Alex's: Alex's fixes some | |
986 |
|
957 | # problems, but it generates empty tracebacks for console errors | ||
987 | def debugger(self,force=False): |
|
958 | # (5 blanks lines) where none should be returned. | |
|
959 | return _fixed_getinnerframes(etb, number_of_lines_of_context, tb_offset) | |||
|
960 | except: | |||
|
961 | # FIXME: I've been getting many crash reports from python 2.3 | |||
|
962 | # users, traceable to inspect.py. If I can find a small test-case | |||
|
963 | # to reproduce this, I should either write a better workaround or | |||
|
964 | # file a bug report against inspect (if that's the real problem). | |||
|
965 | # So far, I haven't been able to find an isolated example to | |||
|
966 | # reproduce the problem. | |||
|
967 | inspect_error() | |||
|
968 | traceback.print_exc(file=self.ostream) | |||
|
969 | info('\nUnfortunately, your original traceback can not be constructed.\n') | |||
|
970 | return None | |||
|
971 | ||||
|
972 | def get_parts_of_chained_exception(self, evalue): | |||
|
973 | def get_chained_exception(exception_value): | |||
|
974 | cause = getattr(exception_value, '__cause__', None) | |||
|
975 | if cause: | |||
|
976 | return cause | |||
|
977 | return getattr(exception_value, '__context__', None) | |||
|
978 | ||||
|
979 | chained_evalue = get_chained_exception(evalue) | |||
|
980 | ||||
|
981 | if chained_evalue: | |||
|
982 | return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__ | |||
|
983 | ||||
|
984 | def structured_traceback(self, etype, evalue, etb, tb_offset=None, | |||
|
985 | number_of_lines_of_context=5): | |||
|
986 | """Return a nice text document describing the traceback.""" | |||
|
987 | ||||
|
988 | formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context, | |||
|
989 | tb_offset) | |||
|
990 | ||||
|
991 | colors = self.Colors # just a shorthand + quicker name lookup | |||
|
992 | colorsnormal = colors.Normal # used a lot | |||
|
993 | head = '%s%s%s' % (colors.topline, '-' * 75, colorsnormal) | |||
|
994 | structured_traceback_parts = [head] | |||
|
995 | if py3compat.PY3: | |||
|
996 | chained_exceptions_tb_offset = 0 | |||
|
997 | lines_of_context = 3 | |||
|
998 | formatted_exceptions = formatted_exception | |||
|
999 | exception = self.get_parts_of_chained_exception(evalue) | |||
|
1000 | if exception: | |||
|
1001 | formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__) | |||
|
1002 | etype, evalue, etb = exception | |||
|
1003 | else: | |||
|
1004 | evalue = None | |||
|
1005 | while evalue: | |||
|
1006 | formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context, | |||
|
1007 | chained_exceptions_tb_offset) | |||
|
1008 | exception = self.get_parts_of_chained_exception(evalue) | |||
|
1009 | ||||
|
1010 | if exception: | |||
|
1011 | formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__) | |||
|
1012 | etype, evalue, etb = exception | |||
|
1013 | else: | |||
|
1014 | evalue = None | |||
|
1015 | ||||
|
1016 | # we want to see exceptions in a reversed order: | |||
|
1017 | # the first exception should be on top | |||
|
1018 | for formatted_exception in reversed(formatted_exceptions): | |||
|
1019 | structured_traceback_parts += formatted_exception | |||
|
1020 | else: | |||
|
1021 | structured_traceback_parts += formatted_exception[0] | |||
|
1022 | ||||
|
1023 | return structured_traceback_parts | |||
|
1024 | ||||
|
1025 | def debugger(self, force=False): | |||
988 | """Call up the pdb debugger if desired, always clean up the tb |
|
1026 | """Call up the pdb debugger if desired, always clean up the tb | |
989 | reference. |
|
1027 | reference. | |
990 |
|
1028 | |||
@@ -1014,7 +1052,7 b' class VerboseTB(TBTools):' | |||||
1014 | with display_trap: |
|
1052 | with display_trap: | |
1015 | self.pdb.reset() |
|
1053 | self.pdb.reset() | |
1016 | # Find the right frame so we don't pop up inside ipython itself |
|
1054 | # Find the right frame so we don't pop up inside ipython itself | |
1017 | if hasattr(self,'tb') and self.tb is not None: |
|
1055 | if hasattr(self, 'tb') and self.tb is not None: | |
1018 | etb = self.tb |
|
1056 | etb = self.tb | |
1019 | else: |
|
1057 | else: | |
1020 | etb = self.tb = sys.last_traceback |
|
1058 | etb = self.tb = sys.last_traceback | |
@@ -1025,7 +1063,7 b' class VerboseTB(TBTools):' | |||||
1025 | self.pdb.botframe = etb.tb_frame |
|
1063 | self.pdb.botframe = etb.tb_frame | |
1026 | self.pdb.interaction(self.tb.tb_frame, self.tb) |
|
1064 | self.pdb.interaction(self.tb.tb_frame, self.tb) | |
1027 |
|
1065 | |||
1028 | if hasattr(self,'tb'): |
|
1066 | if hasattr(self, 'tb'): | |
1029 | del self.tb |
|
1067 | del self.tb | |
1030 |
|
1068 | |||
1031 | def handler(self, info=None): |
|
1069 | def handler(self, info=None): | |
@@ -1050,6 +1088,7 b' class VerboseTB(TBTools):' | |||||
1050 | except KeyboardInterrupt: |
|
1088 | except KeyboardInterrupt: | |
1051 | print("\nKeyboardInterrupt") |
|
1089 | print("\nKeyboardInterrupt") | |
1052 |
|
1090 | |||
|
1091 | ||||
1053 | #---------------------------------------------------------------------------- |
|
1092 | #---------------------------------------------------------------------------- | |
1054 | class FormattedTB(VerboseTB, ListTB): |
|
1093 | class FormattedTB(VerboseTB, ListTB): | |
1055 | """Subclass ListTB but allow calling with a traceback. |
|
1094 | """Subclass ListTB but allow calling with a traceback. | |
@@ -1069,7 +1108,7 b' class FormattedTB(VerboseTB, ListTB):' | |||||
1069 | check_cache=None): |
|
1108 | check_cache=None): | |
1070 |
|
1109 | |||
1071 | # NEVER change the order of this list. Put new modes at the end: |
|
1110 | # NEVER change the order of this list. Put new modes at the end: | |
1072 | self.valid_modes = ['Plain','Context','Verbose'] |
|
1111 | self.valid_modes = ['Plain', 'Context', 'Verbose'] | |
1073 | self.verbose_modes = self.valid_modes[1:3] |
|
1112 | self.verbose_modes = self.valid_modes[1:3] | |
1074 |
|
1113 | |||
1075 | VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, |
|
1114 | VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, | |
@@ -1083,19 +1122,19 b' class FormattedTB(VerboseTB, ListTB):' | |||||
1083 | # set_mode also sets the tb_join_char attribute |
|
1122 | # set_mode also sets the tb_join_char attribute | |
1084 | self.set_mode(mode) |
|
1123 | self.set_mode(mode) | |
1085 |
|
1124 | |||
1086 | def _extract_tb(self,tb): |
|
1125 | def _extract_tb(self, tb): | |
1087 | if tb: |
|
1126 | if tb: | |
1088 | return traceback.extract_tb(tb) |
|
1127 | return traceback.extract_tb(tb) | |
1089 | else: |
|
1128 | else: | |
1090 | return None |
|
1129 | return None | |
1091 |
|
1130 | |||
1092 | def structured_traceback(self, etype, value, tb, tb_offset=None, context=5): |
|
1131 | def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5): | |
1093 | tb_offset = self.tb_offset if tb_offset is None else tb_offset |
|
1132 | tb_offset = self.tb_offset if tb_offset is None else tb_offset | |
1094 | mode = self.mode |
|
1133 | mode = self.mode | |
1095 | if mode in self.verbose_modes: |
|
1134 | if mode in self.verbose_modes: | |
1096 | # Verbose modes need a full traceback |
|
1135 | # Verbose modes need a full traceback | |
1097 | return VerboseTB.structured_traceback( |
|
1136 | return VerboseTB.structured_traceback( | |
1098 | self, etype, value, tb, tb_offset, context |
|
1137 | self, etype, value, tb, tb_offset, number_of_lines_of_context | |
1099 | ) |
|
1138 | ) | |
1100 | else: |
|
1139 | else: | |
1101 | # We must check the source cache because otherwise we can print |
|
1140 | # We must check the source cache because otherwise we can print | |
@@ -1104,7 +1143,7 b' class FormattedTB(VerboseTB, ListTB):' | |||||
1104 | # Now we can extract and format the exception |
|
1143 | # Now we can extract and format the exception | |
1105 | elist = self._extract_tb(tb) |
|
1144 | elist = self._extract_tb(tb) | |
1106 | return ListTB.structured_traceback( |
|
1145 | return ListTB.structured_traceback( | |
1107 | self, etype, value, elist, tb_offset, context |
|
1146 | self, etype, value, elist, tb_offset, number_of_lines_of_context | |
1108 | ) |
|
1147 | ) | |
1109 |
|
1148 | |||
1110 | def stb2text(self, stb): |
|
1149 | def stb2text(self, stb): | |
@@ -1112,18 +1151,18 b' class FormattedTB(VerboseTB, ListTB):' | |||||
1112 | return self.tb_join_char.join(stb) |
|
1151 | return self.tb_join_char.join(stb) | |
1113 |
|
1152 | |||
1114 |
|
1153 | |||
1115 | def set_mode(self,mode=None): |
|
1154 | def set_mode(self, mode=None): | |
1116 | """Switch to the desired mode. |
|
1155 | """Switch to the desired mode. | |
1117 |
|
1156 | |||
1118 | If mode is not specified, cycles through the available modes.""" |
|
1157 | If mode is not specified, cycles through the available modes.""" | |
1119 |
|
1158 | |||
1120 | if not mode: |
|
1159 | if not mode: | |
1121 |
new_idx = ( |
|
1160 | new_idx = (self.valid_modes.index(self.mode) + 1 ) % \ | |
1122 | len(self.valid_modes) |
|
1161 | len(self.valid_modes) | |
1123 | self.mode = self.valid_modes[new_idx] |
|
1162 | self.mode = self.valid_modes[new_idx] | |
1124 | elif mode not in self.valid_modes: |
|
1163 | elif mode not in self.valid_modes: | |
1125 | raise ValueError('Unrecognized mode in FormattedTB: <'+mode+'>\n' |
|
1164 | raise ValueError('Unrecognized mode in FormattedTB: <' + mode + '>\n' | |
1126 | 'Valid modes: '+str(self.valid_modes)) |
|
1165 | 'Valid modes: ' + str(self.valid_modes)) | |
1127 | else: |
|
1166 | else: | |
1128 | self.mode = mode |
|
1167 | self.mode = mode | |
1129 | # include variable details only in 'Verbose' mode |
|
1168 | # include variable details only in 'Verbose' mode | |
@@ -1131,7 +1170,7 b' class FormattedTB(VerboseTB, ListTB):' | |||||
1131 | # Set the join character for generating text tracebacks |
|
1170 | # Set the join character for generating text tracebacks | |
1132 | self.tb_join_char = self._join_chars[self.mode] |
|
1171 | self.tb_join_char = self._join_chars[self.mode] | |
1133 |
|
1172 | |||
1134 | # some convenient shorcuts |
|
1173 | # some convenient shortcuts | |
1135 | def plain(self): |
|
1174 | def plain(self): | |
1136 | self.set_mode(self.valid_modes[0]) |
|
1175 | self.set_mode(self.valid_modes[0]) | |
1137 |
|
1176 | |||
@@ -1141,6 +1180,7 b' class FormattedTB(VerboseTB, ListTB):' | |||||
1141 | def verbose(self): |
|
1180 | def verbose(self): | |
1142 | self.set_mode(self.valid_modes[2]) |
|
1181 | self.set_mode(self.valid_modes[2]) | |
1143 |
|
1182 | |||
|
1183 | ||||
1144 | #---------------------------------------------------------------------------- |
|
1184 | #---------------------------------------------------------------------------- | |
1145 | class AutoFormattedTB(FormattedTB): |
|
1185 | class AutoFormattedTB(FormattedTB): | |
1146 | """A traceback printer which can be called on the fly. |
|
1186 | """A traceback printer which can be called on the fly. | |
@@ -1156,8 +1196,8 b' class AutoFormattedTB(FormattedTB):' | |||||
1156 | AutoTB() # or AutoTB(out=logfile) where logfile is an open file object |
|
1196 | AutoTB() # or AutoTB(out=logfile) where logfile is an open file object | |
1157 | """ |
|
1197 | """ | |
1158 |
|
1198 | |||
1159 | def __call__(self,etype=None,evalue=None,etb=None, |
|
1199 | def __call__(self, etype=None, evalue=None, etb=None, | |
1160 | out=None,tb_offset=None): |
|
1200 | out=None, tb_offset=None): | |
1161 | """Print out a formatted exception traceback. |
|
1201 | """Print out a formatted exception traceback. | |
1162 |
|
1202 | |||
1163 | Optional arguments: |
|
1203 | Optional arguments: | |
@@ -1167,7 +1207,6 b' class AutoFormattedTB(FormattedTB):' | |||||
1167 | per-call basis (this overrides temporarily the instance's tb_offset |
|
1207 | per-call basis (this overrides temporarily the instance's tb_offset | |
1168 | given at initialization time. """ |
|
1208 | given at initialization time. """ | |
1169 |
|
1209 | |||
1170 |
|
||||
1171 | if out is None: |
|
1210 | if out is None: | |
1172 | out = self.ostream |
|
1211 | out = self.ostream | |
1173 | out.flush() |
|
1212 | out.flush() | |
@@ -1182,33 +1221,36 b' class AutoFormattedTB(FormattedTB):' | |||||
1182 | print("\nKeyboardInterrupt") |
|
1221 | print("\nKeyboardInterrupt") | |
1183 |
|
1222 | |||
1184 | def structured_traceback(self, etype=None, value=None, tb=None, |
|
1223 | def structured_traceback(self, etype=None, value=None, tb=None, | |
1185 | tb_offset=None, context=5): |
|
1224 | tb_offset=None, number_of_lines_of_context=5): | |
1186 | if etype is None: |
|
1225 | if etype is None: | |
1187 | etype,value,tb = sys.exc_info() |
|
1226 | etype, value, tb = sys.exc_info() | |
1188 | self.tb = tb |
|
1227 | self.tb = tb | |
1189 | return FormattedTB.structured_traceback( |
|
1228 | return FormattedTB.structured_traceback( | |
1190 | self, etype, value, tb, tb_offset, context) |
|
1229 | self, etype, value, tb, tb_offset, number_of_lines_of_context) | |
|
1230 | ||||
1191 |
|
1231 | |||
1192 | #--------------------------------------------------------------------------- |
|
1232 | #--------------------------------------------------------------------------- | |
1193 |
|
1233 | |||
1194 | # A simple class to preserve Nathan's original functionality. |
|
1234 | # A simple class to preserve Nathan's original functionality. | |
1195 | class ColorTB(FormattedTB): |
|
1235 | class ColorTB(FormattedTB): | |
1196 | """Shorthand to initialize a FormattedTB in Linux colors mode.""" |
|
1236 | """Shorthand to initialize a FormattedTB in Linux colors mode.""" | |
1197 | def __init__(self,color_scheme='Linux',call_pdb=0): |
|
1237 | ||
1198 | FormattedTB.__init__(self,color_scheme=color_scheme, |
|
1238 | def __init__(self, color_scheme='Linux', call_pdb=0): | |
|
1239 | FormattedTB.__init__(self, color_scheme=color_scheme, | |||
1199 | call_pdb=call_pdb) |
|
1240 | call_pdb=call_pdb) | |
1200 |
|
1241 | |||
1201 |
|
1242 | |||
1202 | class SyntaxTB(ListTB): |
|
1243 | class SyntaxTB(ListTB): | |
1203 | """Extension which holds some state: the last exception value""" |
|
1244 | """Extension which holds some state: the last exception value""" | |
1204 |
|
1245 | |||
1205 |
def __init__(self,color_scheme |
|
1246 | def __init__(self, color_scheme='NoColor'): | |
1206 | ListTB.__init__(self,color_scheme) |
|
1247 | ListTB.__init__(self, color_scheme) | |
1207 | self.last_syntax_error = None |
|
1248 | self.last_syntax_error = None | |
1208 |
|
1249 | |||
1209 | def __call__(self, etype, value, elist): |
|
1250 | def __call__(self, etype, value, elist): | |
1210 | self.last_syntax_error = value |
|
1251 | self.last_syntax_error = value | |
1211 | ListTB.__call__(self,etype,value,elist) |
|
1252 | ||
|
1253 | ListTB.__call__(self, etype, value, elist) | |||
1212 |
|
1254 | |||
1213 | def structured_traceback(self, etype, value, elist, tb_offset=None, |
|
1255 | def structured_traceback(self, etype, value, elist, tb_offset=None, | |
1214 | context=5): |
|
1256 | context=5): | |
@@ -1223,7 +1265,7 b' class SyntaxTB(ListTB):' | |||||
1223 | if newtext: |
|
1265 | if newtext: | |
1224 | value.text = newtext |
|
1266 | value.text = newtext | |
1225 | return super(SyntaxTB, self).structured_traceback(etype, value, elist, |
|
1267 | return super(SyntaxTB, self).structured_traceback(etype, value, elist, | |
1226 | tb_offset=tb_offset, context=context) |
|
1268 | tb_offset=tb_offset, context=context) | |
1227 |
|
1269 | |||
1228 | def clear_err_state(self): |
|
1270 | def clear_err_state(self): | |
1229 | """Return the current error state and clear it""" |
|
1271 | """Return the current error state and clear it""" | |
@@ -1236,7 +1278,46 b' class SyntaxTB(ListTB):' | |||||
1236 | return ''.join(stb) |
|
1278 | return ''.join(stb) | |
1237 |
|
1279 | |||
1238 |
|
1280 | |||
|
1281 | # some internal-use functions | |||
|
1282 | def text_repr(value): | |||
|
1283 | """Hopefully pretty robust repr equivalent.""" | |||
|
1284 | # this is pretty horrible but should always return *something* | |||
|
1285 | try: | |||
|
1286 | return pydoc.text.repr(value) | |||
|
1287 | except KeyboardInterrupt: | |||
|
1288 | raise | |||
|
1289 | except: | |||
|
1290 | try: | |||
|
1291 | return repr(value) | |||
|
1292 | except KeyboardInterrupt: | |||
|
1293 | raise | |||
|
1294 | except: | |||
|
1295 | try: | |||
|
1296 | # all still in an except block so we catch | |||
|
1297 | # getattr raising | |||
|
1298 | name = getattr(value, '__name__', None) | |||
|
1299 | if name: | |||
|
1300 | # ick, recursion | |||
|
1301 | return text_repr(name) | |||
|
1302 | klass = getattr(value, '__class__', None) | |||
|
1303 | if klass: | |||
|
1304 | return '%s instance' % text_repr(klass) | |||
|
1305 | except KeyboardInterrupt: | |||
|
1306 | raise | |||
|
1307 | except: | |||
|
1308 | return 'UNRECOVERABLE REPR FAILURE' | |||
|
1309 | ||||
|
1310 | ||||
|
1311 | def eqrepr(value, repr=text_repr): | |||
|
1312 | return '=%s' % repr(value) | |||
|
1313 | ||||
|
1314 | ||||
|
1315 | def nullrepr(value, repr=text_repr): | |||
|
1316 | return '' | |||
|
1317 | ||||
|
1318 | ||||
1239 | #---------------------------------------------------------------------------- |
|
1319 | #---------------------------------------------------------------------------- | |
|
1320 | ||||
1240 | # module testing (minimal) |
|
1321 | # module testing (minimal) | |
1241 | if __name__ == "__main__": |
|
1322 | if __name__ == "__main__": | |
1242 | def spam(c, d_e): |
|
1323 | def spam(c, d_e): |
@@ -136,7 +136,7 b' def extract_zip(fd, dest):' | |||||
136 | os.rename(os.path.join(parent, topdir), dest) |
|
136 | os.rename(os.path.join(parent, topdir), dest) | |
137 |
|
137 | |||
138 |
|
138 | |||
139 |
def install_mathjax(tag=' |
|
139 | def install_mathjax(tag='2.4.0', dest=default_dest, replace=False, file=None, extractor=extract_tar): | |
140 | """Download and/or install MathJax for offline use. |
|
140 | """Download and/or install MathJax for offline use. | |
141 |
|
141 | |||
142 | This will install mathjax to the nbextensions dir in your IPYTHONDIR. |
|
142 | This will install mathjax to the nbextensions dir in your IPYTHONDIR. | |
@@ -150,8 +150,8 b" def install_mathjax(tag='v2.2', dest=default_dest, replace=False, file=None, ext" | |||||
150 | Whether to remove and replace an existing install. |
|
150 | Whether to remove and replace an existing install. | |
151 | dest : str [IPYTHONDIR/nbextensions/mathjax] |
|
151 | dest : str [IPYTHONDIR/nbextensions/mathjax] | |
152 | Where to install mathjax |
|
152 | Where to install mathjax | |
153 |
tag : str [' |
|
153 | tag : str ['2.4.0'] | |
154 |
Which tag to download. Default is ' |
|
154 | Which tag to download. Default is '2.4.0', the current stable release, | |
155 | but alternatives include 'v1.1a' and 'master'. |
|
155 | but alternatives include 'v1.1a' and 'master'. | |
156 | file : file like object [ defualt to content of https://github.com/mathjax/MathJax/tarball/#{tag}] |
|
156 | file : file like object [ defualt to content of https://github.com/mathjax/MathJax/tarball/#{tag}] | |
157 | File handle from which to untar/unzip/... mathjax |
|
157 | File handle from which to untar/unzip/... mathjax |
@@ -80,6 +80,7 b' try:' | |||||
80 | import traceback |
|
80 | import traceback | |
81 | import signal |
|
81 | import signal | |
82 | import codecs |
|
82 | import codecs | |
|
83 | import stat | |||
83 | except ImportError: # pragma: no cover |
|
84 | except ImportError: # pragma: no cover | |
84 | err = sys.exc_info()[1] |
|
85 | err = sys.exc_info()[1] | |
85 | raise ImportError(str(err) + ''' |
|
86 | raise ImportError(str(err) + ''' | |
@@ -87,7 +88,7 b' except ImportError: # pragma: no cover' | |||||
87 | A critical module was not found. Probably this operating system does not |
|
88 | A critical module was not found. Probably this operating system does not | |
88 | support it. Pexpect is intended for UNIX-like operating systems.''') |
|
89 | support it. Pexpect is intended for UNIX-like operating systems.''') | |
89 |
|
90 | |||
90 |
__version__ = '3. |
|
91 | __version__ = '3.3' | |
91 | __revision__ = '' |
|
92 | __revision__ = '' | |
92 | __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu', |
|
93 | __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu', | |
93 | 'which', 'split_command_line', '__version__', '__revision__'] |
|
94 | 'which', 'split_command_line', '__version__', '__revision__'] | |
@@ -284,6 +285,7 b' class spawn(object):' | |||||
284 | def _chr(c): |
|
285 | def _chr(c): | |
285 | return bytes([c]) |
|
286 | return bytes([c]) | |
286 | linesep = os.linesep.encode('ascii') |
|
287 | linesep = os.linesep.encode('ascii') | |
|
288 | crlf = '\r\n'.encode('ascii') | |||
287 |
|
289 | |||
288 | @staticmethod |
|
290 | @staticmethod | |
289 | def write_to_stdout(b): |
|
291 | def write_to_stdout(b): | |
@@ -296,13 +298,14 b' class spawn(object):' | |||||
296 | allowed_string_types = (basestring,) # analysis:ignore |
|
298 | allowed_string_types = (basestring,) # analysis:ignore | |
297 | _chr = staticmethod(chr) |
|
299 | _chr = staticmethod(chr) | |
298 | linesep = os.linesep |
|
300 | linesep = os.linesep | |
|
301 | crlf = '\r\n' | |||
299 | write_to_stdout = sys.stdout.write |
|
302 | write_to_stdout = sys.stdout.write | |
300 |
|
303 | |||
301 | encoding = None |
|
304 | encoding = None | |
302 |
|
305 | |||
303 | def __init__(self, command, args=[], timeout=30, maxread=2000, |
|
306 | def __init__(self, command, args=[], timeout=30, maxread=2000, | |
304 | searchwindowsize=None, logfile=None, cwd=None, env=None, |
|
307 | searchwindowsize=None, logfile=None, cwd=None, env=None, | |
305 | ignore_sighup=True): |
|
308 | ignore_sighup=True, echo=True): | |
306 |
|
309 | |||
307 | '''This is the constructor. The command parameter may be a string that |
|
310 | '''This is the constructor. The command parameter may be a string that | |
308 | includes a command and any arguments to the command. For example:: |
|
311 | includes a command and any arguments to the command. For example:: | |
@@ -415,7 +418,16 b' class spawn(object):' | |||||
415 | signalstatus will store the signal value and exitstatus will be None. |
|
418 | signalstatus will store the signal value and exitstatus will be None. | |
416 | If you need more detail you can also read the self.status member which |
|
419 | If you need more detail you can also read the self.status member which | |
417 | stores the status returned by os.waitpid. You can interpret this using |
|
420 | stores the status returned by os.waitpid. You can interpret this using | |
418 |
os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. |
|
421 | os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. | |
|
422 | ||||
|
423 | The echo attribute may be set to False to disable echoing of input. | |||
|
424 | As a pseudo-terminal, all input echoed by the "keyboard" (send() | |||
|
425 | or sendline()) will be repeated to output. For many cases, it is | |||
|
426 | not desirable to have echo enabled, and it may be later disabled | |||
|
427 | using setecho(False) followed by waitnoecho(). However, for some | |||
|
428 | platforms such as Solaris, this is not possible, and should be | |||
|
429 | disabled immediately on spawn. | |||
|
430 | ''' | |||
419 |
|
431 | |||
420 | self.STDIN_FILENO = pty.STDIN_FILENO |
|
432 | self.STDIN_FILENO = pty.STDIN_FILENO | |
421 | self.STDOUT_FILENO = pty.STDOUT_FILENO |
|
433 | self.STDOUT_FILENO = pty.STDOUT_FILENO | |
@@ -437,7 +449,7 b' class spawn(object):' | |||||
437 | self.status = None |
|
449 | self.status = None | |
438 | self.flag_eof = False |
|
450 | self.flag_eof = False | |
439 | self.pid = None |
|
451 | self.pid = None | |
440 |
# the chil |
|
452 | # the child file descriptor is initially closed | |
441 | self.child_fd = -1 |
|
453 | self.child_fd = -1 | |
442 | self.timeout = timeout |
|
454 | self.timeout = timeout | |
443 | self.delimiter = EOF |
|
455 | self.delimiter = EOF | |
@@ -466,16 +478,30 b' class spawn(object):' | |||||
466 | self.closed = True |
|
478 | self.closed = True | |
467 | self.cwd = cwd |
|
479 | self.cwd = cwd | |
468 | self.env = env |
|
480 | self.env = env | |
|
481 | self.echo = echo | |||
469 | self.ignore_sighup = ignore_sighup |
|
482 | self.ignore_sighup = ignore_sighup | |
|
483 | _platform = sys.platform.lower() | |||
470 | # This flags if we are running on irix |
|
484 | # This flags if we are running on irix | |
471 |
self.__irix_hack = |
|
485 | self.__irix_hack = _platform.startswith('irix') | |
472 | # Solaris uses internal __fork_pty(). All others use pty.fork(). |
|
486 | # Solaris uses internal __fork_pty(). All others use pty.fork(). | |
473 | if ((sys.platform.lower().find('solaris') >= 0) |
|
487 | self.use_native_pty_fork = not ( | |
474 | or (sys.platform.lower().find('sunos5') >= 0)): |
|
488 | _platform.startswith('solaris') or | |
475 | self.use_native_pty_fork = False |
|
489 | _platform.startswith('sunos')) | |
476 | else: |
|
490 | # inherit EOF and INTR definitions from controlling process. | |
477 | self.use_native_pty_fork = True |
|
491 | try: | |
478 |
|
492 | from termios import VEOF, VINTR | ||
|
493 | fd = sys.__stdin__.fileno() | |||
|
494 | self._INTR = ord(termios.tcgetattr(fd)[6][VINTR]) | |||
|
495 | self._EOF = ord(termios.tcgetattr(fd)[6][VEOF]) | |||
|
496 | except (ImportError, OSError, IOError, termios.error): | |||
|
497 | # unless the controlling process is also not a terminal, | |||
|
498 | # such as cron(1). Fall-back to using CEOF and CINTR. | |||
|
499 | try: | |||
|
500 | from termios import CEOF, CINTR | |||
|
501 | (self._INTR, self._EOF) = (CINTR, CEOF) | |||
|
502 | except ImportError: | |||
|
503 | # ^C, ^D | |||
|
504 | (self._INTR, self._EOF) = (3, 4) | |||
479 | # Support subclasses that do not use command or args. |
|
505 | # Support subclasses that do not use command or args. | |
480 | if command is None: |
|
506 | if command is None: | |
481 | self.command = None |
|
507 | self.command = None | |
@@ -599,33 +625,39 b' class spawn(object):' | |||||
599 | if self.use_native_pty_fork: |
|
625 | if self.use_native_pty_fork: | |
600 | try: |
|
626 | try: | |
601 | self.pid, self.child_fd = pty.fork() |
|
627 | self.pid, self.child_fd = pty.fork() | |
602 | except OSError: |
|
628 | except OSError: # pragma: no cover | |
603 | err = sys.exc_info()[1] |
|
629 | err = sys.exc_info()[1] | |
604 | raise ExceptionPexpect('pty.fork() failed: ' + str(err)) |
|
630 | raise ExceptionPexpect('pty.fork() failed: ' + str(err)) | |
605 | else: |
|
631 | else: | |
606 | # Use internal __fork_pty |
|
632 | # Use internal __fork_pty | |
607 | self.pid, self.child_fd = self.__fork_pty() |
|
633 | self.pid, self.child_fd = self.__fork_pty() | |
608 |
|
634 | |||
609 | if self.pid == 0: |
|
635 | # Some platforms must call setwinsize() and setecho() from the | |
|
636 | # child process, and others from the master process. We do both, | |||
|
637 | # allowing IOError for either. | |||
|
638 | ||||
|
639 | if self.pid == pty.CHILD: | |||
610 | # Child |
|
640 | # Child | |
|
641 | self.child_fd = self.STDIN_FILENO | |||
|
642 | ||||
|
643 | # set default window size of 24 rows by 80 columns | |||
611 | try: |
|
644 | try: | |
612 | # used by setwinsize() |
|
|||
613 | self.child_fd = sys.stdout.fileno() |
|
|||
614 | self.setwinsize(24, 80) |
|
645 | self.setwinsize(24, 80) | |
615 | # which exception, shouldnt' we catch explicitly .. ? |
|
646 | except IOError as err: | |
616 | except: |
|
647 | if err.args[0] not in (errno.EINVAL, errno.ENOTTY): | |
617 | # Some platforms do not like setwinsize (Cygwin). |
|
648 | raise | |
618 | # This will cause problem when running applications that |
|
649 | ||
619 | # are very picky about window size. |
|
650 | # disable echo if spawn argument echo was unset | |
620 | # This is a serious limitation, but not a show stopper. |
|
651 | if not self.echo: | |
621 |
|
|
652 | try: | |
|
653 | self.setecho(self.echo) | |||
|
654 | except (IOError, termios.error) as err: | |||
|
655 | if err.args[0] not in (errno.EINVAL, errno.ENOTTY): | |||
|
656 | raise | |||
|
657 | ||||
622 | # Do not allow child to inherit open file descriptors from parent. |
|
658 | # Do not allow child to inherit open file descriptors from parent. | |
623 | max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0] |
|
659 | max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0] | |
624 |
|
|
660 | os.closerange(3, max_fd) | |
625 | try: |
|
|||
626 | os.close(i) |
|
|||
627 | except OSError: |
|
|||
628 | pass |
|
|||
629 |
|
661 | |||
630 | if self.ignore_sighup: |
|
662 | if self.ignore_sighup: | |
631 | signal.signal(signal.SIGHUP, signal.SIG_IGN) |
|
663 | signal.signal(signal.SIGHUP, signal.SIG_IGN) | |
@@ -638,6 +670,13 b' class spawn(object):' | |||||
638 | os.execvpe(self.command, self.args, self.env) |
|
670 | os.execvpe(self.command, self.args, self.env) | |
639 |
|
671 | |||
640 | # Parent |
|
672 | # Parent | |
|
673 | try: | |||
|
674 | self.setwinsize(24, 80) | |||
|
675 | except IOError as err: | |||
|
676 | if err.args[0] not in (errno.EINVAL, errno.ENOTTY): | |||
|
677 | raise | |||
|
678 | ||||
|
679 | ||||
641 | self.terminated = False |
|
680 | self.terminated = False | |
642 | self.closed = False |
|
681 | self.closed = False | |
643 |
|
682 | |||
@@ -660,19 +699,15 b' class spawn(object):' | |||||
660 | raise ExceptionPexpect("Could not open with os.openpty().") |
|
699 | raise ExceptionPexpect("Could not open with os.openpty().") | |
661 |
|
700 | |||
662 | pid = os.fork() |
|
701 | pid = os.fork() | |
663 |
if pid |
|
702 | if pid == pty.CHILD: | |
664 | raise ExceptionPexpect("Failed os.fork().") |
|
|||
665 | elif pid == 0: |
|
|||
666 | # Child. |
|
703 | # Child. | |
667 | os.close(parent_fd) |
|
704 | os.close(parent_fd) | |
668 | self.__pty_make_controlling_tty(child_fd) |
|
705 | self.__pty_make_controlling_tty(child_fd) | |
669 |
|
706 | |||
670 |
os.dup2(child_fd, |
|
707 | os.dup2(child_fd, self.STDIN_FILENO) | |
671 |
os.dup2(child_fd, |
|
708 | os.dup2(child_fd, self.STDOUT_FILENO) | |
672 |
os.dup2(child_fd, |
|
709 | os.dup2(child_fd, self.STDERR_FILENO) | |
673 |
|
710 | |||
674 | if child_fd > 2: |
|
|||
675 | os.close(child_fd) |
|
|||
676 | else: |
|
711 | else: | |
677 | # Parent. |
|
712 | # Parent. | |
678 | os.close(child_fd) |
|
713 | os.close(child_fd) | |
@@ -686,44 +721,36 b' class spawn(object):' | |||||
686 |
|
721 | |||
687 | child_name = os.ttyname(tty_fd) |
|
722 | child_name = os.ttyname(tty_fd) | |
688 |
|
723 | |||
689 |
# Disconnect from controlling tty |
|
724 | # Disconnect from controlling tty, if any. Raises OSError of ENXIO | |
|
725 | # if there was no controlling tty to begin with, such as when | |||
|
726 | # executed by a cron(1) job. | |||
690 | try: |
|
727 | try: | |
691 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) |
|
728 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) | |
692 | if fd >= 0: |
|
729 | os.close(fd) | |
693 | os.close(fd) |
|
730 | except OSError as err: | |
694 | # which exception, shouldnt' we catch explicitly .. ? |
|
731 | if err.errno != errno.ENXIO: | |
695 | except: |
|
732 | raise | |
696 | # Already disconnected. This happens if running inside cron. |
|
|||
697 | pass |
|
|||
698 |
|
733 | |||
699 | os.setsid() |
|
734 | os.setsid() | |
700 |
|
735 | |||
701 | # Verify we are disconnected from controlling tty |
|
736 | # Verify we are disconnected from controlling tty by attempting to open | |
702 | # by attempting to open it again. |
|
737 | # it again. We expect that OSError of ENXIO should always be raised. | |
703 | try: |
|
738 | try: | |
704 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) |
|
739 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) | |
705 | if fd >= 0: |
|
740 | os.close(fd) | |
706 | os.close(fd) |
|
741 | raise ExceptionPexpect("OSError of errno.ENXIO should be raised.") | |
707 | raise ExceptionPexpect('Failed to disconnect from ' + |
|
742 | except OSError as err: | |
708 | 'controlling tty. It is still possible to open /dev/tty.') |
|
743 | if err.errno != errno.ENXIO: | |
709 | # which exception, shouldnt' we catch explicitly .. ? |
|
744 | raise | |
710 | except: |
|
|||
711 | # Good! We are disconnected from a controlling tty. |
|
|||
712 | pass |
|
|||
713 |
|
745 | |||
714 | # Verify we can open child pty. |
|
746 | # Verify we can open child pty. | |
715 | fd = os.open(child_name, os.O_RDWR) |
|
747 | fd = os.open(child_name, os.O_RDWR) | |
716 | if fd < 0: |
|
748 | os.close(fd) | |
717 | raise ExceptionPexpect("Could not open child pty, " + child_name) |
|
|||
718 | else: |
|
|||
719 | os.close(fd) |
|
|||
720 |
|
749 | |||
721 | # Verify we now have a controlling tty. |
|
750 | # Verify we now have a controlling tty. | |
722 | fd = os.open("/dev/tty", os.O_WRONLY) |
|
751 | fd = os.open("/dev/tty", os.O_WRONLY) | |
723 | if fd < 0: |
|
752 | os.close(fd) | |
724 | raise ExceptionPexpect("Could not open controlling tty, /dev/tty") |
|
753 | ||
725 | else: |
|
|||
726 | os.close(fd) |
|
|||
727 |
|
754 | |||
728 | def fileno(self): |
|
755 | def fileno(self): | |
729 | '''This returns the file descriptor of the pty for the child. |
|
756 | '''This returns the file descriptor of the pty for the child. | |
@@ -757,7 +784,12 b' class spawn(object):' | |||||
757 |
|
784 | |||
758 | def isatty(self): |
|
785 | def isatty(self): | |
759 | '''This returns True if the file descriptor is open and connected to a |
|
786 | '''This returns True if the file descriptor is open and connected to a | |
760 |
tty(-like) device, else False. |
|
787 | tty(-like) device, else False. | |
|
788 | ||||
|
789 | On SVR4-style platforms implementing streams, such as SunOS and HP-UX, | |||
|
790 | the child pty may not appear as a terminal device. This means | |||
|
791 | methods such as setecho(), setwinsize(), getwinsize() may raise an | |||
|
792 | IOError. ''' | |||
761 |
|
793 | |||
762 | return os.isatty(self.child_fd) |
|
794 | return os.isatty(self.child_fd) | |
763 |
|
795 | |||
@@ -794,12 +826,20 b' class spawn(object):' | |||||
794 | def getecho(self): |
|
826 | def getecho(self): | |
795 | '''This returns the terminal echo mode. This returns True if echo is |
|
827 | '''This returns the terminal echo mode. This returns True if echo is | |
796 | on or False if echo is off. Child applications that are expecting you |
|
828 | on or False if echo is off. Child applications that are expecting you | |
797 |
to enter a password often set ECHO False. See waitnoecho(). |
|
829 | to enter a password often set ECHO False. See waitnoecho(). | |
798 |
|
|
830 | ||
799 | attr = termios.tcgetattr(self.child_fd) |
|
831 | Not supported on platforms where ``isatty()`` returns False. ''' | |
800 | if attr[3] & termios.ECHO: |
|
832 | ||
801 | return True |
|
833 | try: | |
802 | return False |
|
834 | attr = termios.tcgetattr(self.child_fd) | |
|
835 | except termios.error as err: | |||
|
836 | errmsg = 'getecho() may not be called on this platform' | |||
|
837 | if err.args[0] == errno.EINVAL: | |||
|
838 | raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) | |||
|
839 | raise | |||
|
840 | ||||
|
841 | self.echo = bool(attr[3] & termios.ECHO) | |||
|
842 | return self.echo | |||
803 |
|
843 | |||
804 | def setecho(self, state): |
|
844 | def setecho(self, state): | |
805 | '''This sets the terminal echo mode on or off. Note that anything the |
|
845 | '''This sets the terminal echo mode on or off. Note that anything the | |
@@ -829,18 +869,35 b' class spawn(object):' | |||||
829 | p.expect(['1234']) |
|
869 | p.expect(['1234']) | |
830 | p.expect(['abcd']) |
|
870 | p.expect(['abcd']) | |
831 | p.expect(['wxyz']) |
|
871 | p.expect(['wxyz']) | |
|
872 | ||||
|
873 | ||||
|
874 | Not supported on platforms where ``isatty()`` returns False. | |||
832 | ''' |
|
875 | ''' | |
833 |
|
876 | |||
834 | self.child_fd |
|
877 | errmsg = 'setecho() may not be called on this platform' | |
835 | attr = termios.tcgetattr(self.child_fd) |
|
878 | ||
|
879 | try: | |||
|
880 | attr = termios.tcgetattr(self.child_fd) | |||
|
881 | except termios.error as err: | |||
|
882 | if err.args[0] == errno.EINVAL: | |||
|
883 | raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) | |||
|
884 | raise | |||
|
885 | ||||
836 | if state: |
|
886 | if state: | |
837 | attr[3] = attr[3] | termios.ECHO |
|
887 | attr[3] = attr[3] | termios.ECHO | |
838 | else: |
|
888 | else: | |
839 | attr[3] = attr[3] & ~termios.ECHO |
|
889 | attr[3] = attr[3] & ~termios.ECHO | |
840 | # I tried TCSADRAIN and TCSAFLUSH, but |
|
890 | ||
841 | # these were inconsistent and blocked on some platforms. |
|
891 | try: | |
842 | # TCSADRAIN would probably be ideal if it worked. |
|
892 | # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and | |
843 | termios.tcsetattr(self.child_fd, termios.TCSANOW, attr) |
|
893 | # blocked on some platforms. TCSADRAIN would probably be ideal. | |
|
894 | termios.tcsetattr(self.child_fd, termios.TCSANOW, attr) | |||
|
895 | except IOError as err: | |||
|
896 | if err.args[0] == errno.EINVAL: | |||
|
897 | raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) | |||
|
898 | raise | |||
|
899 | ||||
|
900 | self.echo = state | |||
844 |
|
901 | |||
845 | def _log(self, s, direction): |
|
902 | def _log(self, s, direction): | |
846 | if self.logfile is not None: |
|
903 | if self.logfile is not None: | |
@@ -913,12 +970,14 b' class spawn(object):' | |||||
913 | if self.child_fd in r: |
|
970 | if self.child_fd in r: | |
914 | try: |
|
971 | try: | |
915 | s = os.read(self.child_fd, size) |
|
972 | s = os.read(self.child_fd, size) | |
916 | except OSError: |
|
973 | except OSError as err: | |
917 | # Linux does this |
|
974 | if err.args[0] == errno.EIO: | |
918 | self.flag_eof = True |
|
975 | # Linux-style EOF | |
919 | raise EOF('End Of File (EOF). Exception style platform.') |
|
976 | self.flag_eof = True | |
|
977 | raise EOF('End Of File (EOF). Exception style platform.') | |||
|
978 | raise | |||
920 | if s == b'': |
|
979 | if s == b'': | |
921 |
# BSD |
|
980 | # BSD-style EOF | |
922 | self.flag_eof = True |
|
981 | self.flag_eof = True | |
923 | raise EOF('End Of File (EOF). Empty string style platform.') |
|
982 | raise EOF('End Of File (EOF). Empty string style platform.') | |
924 |
|
983 | |||
@@ -926,7 +985,7 b' class spawn(object):' | |||||
926 | self._log(s, 'read') |
|
985 | self._log(s, 'read') | |
927 | return s |
|
986 | return s | |
928 |
|
987 | |||
929 | raise ExceptionPexpect('Reached an unexpected state.') |
|
988 | raise ExceptionPexpect('Reached an unexpected state.') # pragma: no cover | |
930 |
|
989 | |||
931 | def read(self, size=-1): |
|
990 | def read(self, size=-1): | |
932 | '''This reads at most "size" bytes from the file (less if the read hits |
|
991 | '''This reads at most "size" bytes from the file (less if the read hits | |
@@ -972,9 +1031,9 b' class spawn(object):' | |||||
972 | if size == 0: |
|
1031 | if size == 0: | |
973 | return self.string_type() |
|
1032 | return self.string_type() | |
974 | # delimiter default is EOF |
|
1033 | # delimiter default is EOF | |
975 |
index = self.expect([ |
|
1034 | index = self.expect([self.crlf, self.delimiter]) | |
976 | if index == 0: |
|
1035 | if index == 0: | |
977 |
return self.before + |
|
1036 | return self.before + self.crlf | |
978 | else: |
|
1037 | else: | |
979 | return self.before |
|
1038 | return self.before | |
980 |
|
1039 | |||
@@ -1075,40 +1134,14 b' class spawn(object):' | |||||
1075 | It is the responsibility of the caller to ensure the eof is sent at the |
|
1134 | It is the responsibility of the caller to ensure the eof is sent at the | |
1076 | beginning of a line. ''' |
|
1135 | beginning of a line. ''' | |
1077 |
|
1136 | |||
1078 | ### Hmmm... how do I send an EOF? |
|
1137 | self.send(self._chr(self._EOF)) | |
1079 | ###C if ((m = write(pty, *buf, p - *buf)) < 0) |
|
|||
1080 | ###C return (errno == EWOULDBLOCK) ? n : -1; |
|
|||
1081 | #fd = sys.stdin.fileno() |
|
|||
1082 | #old = termios.tcgetattr(fd) # remember current state |
|
|||
1083 | #attr = termios.tcgetattr(fd) |
|
|||
1084 | #attr[3] = attr[3] | termios.ICANON # ICANON must be set to see EOF |
|
|||
1085 | #try: # use try/finally to ensure state gets restored |
|
|||
1086 | # termios.tcsetattr(fd, termios.TCSADRAIN, attr) |
|
|||
1087 | # if hasattr(termios, 'CEOF'): |
|
|||
1088 | # os.write(self.child_fd, '%c' % termios.CEOF) |
|
|||
1089 | # else: |
|
|||
1090 | # # Silly platform does not define CEOF so assume CTRL-D |
|
|||
1091 | # os.write(self.child_fd, '%c' % 4) |
|
|||
1092 | #finally: # restore state |
|
|||
1093 | # termios.tcsetattr(fd, termios.TCSADRAIN, old) |
|
|||
1094 | if hasattr(termios, 'VEOF'): |
|
|||
1095 | char = ord(termios.tcgetattr(self.child_fd)[6][termios.VEOF]) |
|
|||
1096 | else: |
|
|||
1097 | # platform does not define VEOF so assume CTRL-D |
|
|||
1098 | char = 4 |
|
|||
1099 | self.send(self._chr(char)) |
|
|||
1100 |
|
1138 | |||
1101 | def sendintr(self): |
|
1139 | def sendintr(self): | |
1102 |
|
1140 | |||
1103 | '''This sends a SIGINT to the child. It does not require |
|
1141 | '''This sends a SIGINT to the child. It does not require | |
1104 | the SIGINT to be the first character on a line. ''' |
|
1142 | the SIGINT to be the first character on a line. ''' | |
1105 |
|
1143 | |||
1106 | if hasattr(termios, 'VINTR'): |
|
1144 | self.send(self._chr(self._INTR)) | |
1107 | char = ord(termios.tcgetattr(self.child_fd)[6][termios.VINTR]) |
|
|||
1108 | else: |
|
|||
1109 | # platform does not define VINTR so assume CTRL-C |
|
|||
1110 | char = 3 |
|
|||
1111 | self.send(self._chr(char)) |
|
|||
1112 |
|
1145 | |||
1113 | def eof(self): |
|
1146 | def eof(self): | |
1114 |
|
1147 | |||
@@ -1181,7 +1214,7 b' class spawn(object):' | |||||
1181 | self.exitstatus = None |
|
1214 | self.exitstatus = None | |
1182 | self.signalstatus = os.WTERMSIG(status) |
|
1215 | self.signalstatus = os.WTERMSIG(status) | |
1183 | self.terminated = True |
|
1216 | self.terminated = True | |
1184 | elif os.WIFSTOPPED(status): |
|
1217 | elif os.WIFSTOPPED(status): # pragma: no cover | |
1185 | # You can't call wait() on a child process in the stopped state. |
|
1218 | # You can't call wait() on a child process in the stopped state. | |
1186 | raise ExceptionPexpect('Called wait() on a stopped child ' + |
|
1219 | raise ExceptionPexpect('Called wait() on a stopped child ' + | |
1187 | 'process. This is not supported. Is some other ' + |
|
1220 | 'process. This is not supported. Is some other ' + | |
@@ -1201,7 +1234,7 b' class spawn(object):' | |||||
1201 |
|
1234 | |||
1202 | if self.flag_eof: |
|
1235 | if self.flag_eof: | |
1203 | # This is for Linux, which requires the blocking form |
|
1236 | # This is for Linux, which requires the blocking form | |
1204 |
# of waitpid to |
|
1237 | # of waitpid to get the status of a defunct process. | |
1205 | # This is super-lame. The flag_eof would have been set |
|
1238 | # This is super-lame. The flag_eof would have been set | |
1206 | # in read_nonblocking(), so this should be safe. |
|
1239 | # in read_nonblocking(), so this should be safe. | |
1207 | waitpid_options = 0 |
|
1240 | waitpid_options = 0 | |
@@ -1229,7 +1262,7 b' class spawn(object):' | |||||
1229 | try: |
|
1262 | try: | |
1230 | ### os.WNOHANG) # Solaris! |
|
1263 | ### os.WNOHANG) # Solaris! | |
1231 | pid, status = os.waitpid(self.pid, waitpid_options) |
|
1264 | pid, status = os.waitpid(self.pid, waitpid_options) | |
1232 | except OSError as e: |
|
1265 | except OSError as e: # pragma: no cover | |
1233 | # This should never happen... |
|
1266 | # This should never happen... | |
1234 | if e.errno == errno.ECHILD: |
|
1267 | if e.errno == errno.ECHILD: | |
1235 | raise ExceptionPexpect('isalive() encountered condition ' + |
|
1268 | raise ExceptionPexpect('isalive() encountered condition ' + | |
@@ -1643,10 +1676,14 b' class spawn(object):' | |||||
1643 | if self.child_fd in r: |
|
1676 | if self.child_fd in r: | |
1644 | try: |
|
1677 | try: | |
1645 | data = self.__interact_read(self.child_fd) |
|
1678 | data = self.__interact_read(self.child_fd) | |
1646 | except OSError as e: |
|
1679 | except OSError as err: | |
1647 | # The subprocess may have closed before we get to reading it |
|
1680 | if err.args[0] == errno.EIO: | |
1648 | if e.errno != errno.EIO: |
|
1681 | # Linux-style EOF | |
1649 |
r |
|
1682 | break | |
|
1683 | raise | |||
|
1684 | if data == b'': | |||
|
1685 | # BSD-style EOF | |||
|
1686 | break | |||
1650 | if output_filter: |
|
1687 | if output_filter: | |
1651 | data = output_filter(data) |
|
1688 | data = output_filter(data) | |
1652 | if self.logfile is not None: |
|
1689 | if self.logfile is not None: | |
@@ -1695,7 +1732,7 b' class spawn(object):' | |||||
1695 | ############################################################################## |
|
1732 | ############################################################################## | |
1696 | # The following methods are no longer supported or allowed. |
|
1733 | # The following methods are no longer supported or allowed. | |
1697 |
|
1734 | |||
1698 | def setmaxread(self, maxread): |
|
1735 | def setmaxread(self, maxread): # pragma: no cover | |
1699 |
|
1736 | |||
1700 | '''This method is no longer supported or allowed. I don't like getters |
|
1737 | '''This method is no longer supported or allowed. I don't like getters | |
1701 | and setters without a good reason. ''' |
|
1738 | and setters without a good reason. ''' | |
@@ -1704,7 +1741,7 b' class spawn(object):' | |||||
1704 | 'or allowed. Just assign a value to the ' + |
|
1741 | 'or allowed. Just assign a value to the ' + | |
1705 | 'maxread member variable.') |
|
1742 | 'maxread member variable.') | |
1706 |
|
1743 | |||
1707 | def setlog(self, fileobject): |
|
1744 | def setlog(self, fileobject): # pragma: no cover | |
1708 |
|
1745 | |||
1709 | '''This method is no longer supported or allowed. |
|
1746 | '''This method is no longer supported or allowed. | |
1710 | ''' |
|
1747 | ''' | |
@@ -1732,11 +1769,13 b' class spawnu(spawn):' | |||||
1732 | allowed_string_types = (str, ) |
|
1769 | allowed_string_types = (str, ) | |
1733 | _chr = staticmethod(chr) |
|
1770 | _chr = staticmethod(chr) | |
1734 | linesep = os.linesep |
|
1771 | linesep = os.linesep | |
|
1772 | crlf = '\r\n' | |||
1735 | else: |
|
1773 | else: | |
1736 | string_type = unicode |
|
1774 | string_type = unicode | |
1737 | allowed_string_types = (unicode, ) |
|
1775 | allowed_string_types = (unicode, ) | |
1738 | _chr = staticmethod(unichr) |
|
1776 | _chr = staticmethod(unichr) | |
1739 | linesep = os.linesep.decode('ascii') |
|
1777 | linesep = os.linesep.decode('ascii') | |
|
1778 | crlf = '\r\n'.decode('ascii') | |||
1740 | # This can handle unicode in both Python 2 and 3 |
|
1779 | # This can handle unicode in both Python 2 and 3 | |
1741 | write_to_stdout = sys.stdout.write |
|
1780 | write_to_stdout = sys.stdout.write | |
1742 |
|
1781 | |||
@@ -1959,16 +1998,56 b' class searcher_re(object):' | |||||
1959 | return best_index |
|
1998 | return best_index | |
1960 |
|
1999 | |||
1961 |
|
2000 | |||
1962 | def which(filename): |
|
2001 | def is_executable_file(path): | |
|
2002 | """Checks that path is an executable regular file (or a symlink to a file). | |||
|
2003 | ||||
|
2004 | This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``, but | |||
|
2005 | on some platforms :func:`os.access` gives us the wrong answer, so this | |||
|
2006 | checks permission bits directly. | |||
|
2007 | """ | |||
|
2008 | # follow symlinks, | |||
|
2009 | fpath = os.path.realpath(path) | |||
1963 |
|
2010 | |||
|
2011 | # return False for non-files (directories, fifo, etc.) | |||
|
2012 | if not os.path.isfile(fpath): | |||
|
2013 | return False | |||
|
2014 | ||||
|
2015 | # On Solaris, etc., "If the process has appropriate privileges, an | |||
|
2016 | # implementation may indicate success for X_OK even if none of the | |||
|
2017 | # execute file permission bits are set." | |||
|
2018 | # | |||
|
2019 | # For this reason, it is necessary to explicitly check st_mode | |||
|
2020 | ||||
|
2021 | # get file mode using os.stat, and check if `other', | |||
|
2022 | # that is anybody, may read and execute. | |||
|
2023 | mode = os.stat(fpath).st_mode | |||
|
2024 | if mode & stat.S_IROTH and mode & stat.S_IXOTH: | |||
|
2025 | return True | |||
|
2026 | ||||
|
2027 | # get current user's group ids, and check if `group', | |||
|
2028 | # when matching ours, may read and execute. | |||
|
2029 | user_gids = os.getgroups() + [os.getgid()] | |||
|
2030 | if (os.stat(fpath).st_gid in user_gids and | |||
|
2031 | mode & stat.S_IRGRP and mode & stat.S_IXGRP): | |||
|
2032 | return True | |||
|
2033 | ||||
|
2034 | # finally, if file owner matches our effective userid, | |||
|
2035 | # check if `user', may read and execute. | |||
|
2036 | user_gids = os.getgroups() + [os.getgid()] | |||
|
2037 | if (os.stat(fpath).st_uid == os.geteuid() and | |||
|
2038 | mode & stat.S_IRUSR and mode & stat.S_IXUSR): | |||
|
2039 | return True | |||
|
2040 | ||||
|
2041 | return False | |||
|
2042 | ||||
|
2043 | def which(filename): | |||
1964 | '''This takes a given filename; tries to find it in the environment path; |
|
2044 | '''This takes a given filename; tries to find it in the environment path; | |
1965 | then checks if it is executable. This returns the full path to the filename |
|
2045 | then checks if it is executable. This returns the full path to the filename | |
1966 | if found and executable. Otherwise this returns None.''' |
|
2046 | if found and executable. Otherwise this returns None.''' | |
1967 |
|
2047 | |||
1968 | # Special case where filename contains an explicit path. |
|
2048 | # Special case where filename contains an explicit path. | |
1969 | if os.path.dirname(filename) != '': |
|
2049 | if os.path.dirname(filename) != '' and is_executable_file(filename): | |
1970 | if os.access(filename, os.X_OK): |
|
2050 | return filename | |
1971 | return filename |
|
|||
1972 | if 'PATH' not in os.environ or os.environ['PATH'] == '': |
|
2051 | if 'PATH' not in os.environ or os.environ['PATH'] == '': | |
1973 | p = os.defpath |
|
2052 | p = os.defpath | |
1974 | else: |
|
2053 | else: | |
@@ -1976,7 +2055,7 b' def which(filename):' | |||||
1976 | pathlist = p.split(os.pathsep) |
|
2055 | pathlist = p.split(os.pathsep) | |
1977 | for path in pathlist: |
|
2056 | for path in pathlist: | |
1978 | ff = os.path.join(path, filename) |
|
2057 | ff = os.path.join(path, filename) | |
1979 | if os.access(ff, os.X_OK): |
|
2058 | if is_executable_file(ff): | |
1980 | return ff |
|
2059 | return ff | |
1981 | return None |
|
2060 | return None | |
1982 |
|
2061 | |||
@@ -2041,4 +2120,4 b' def split_command_line(command_line):' | |||||
2041 | arg_list.append(arg) |
|
2120 | arg_list.append(arg) | |
2042 | return arg_list |
|
2121 | return arg_list | |
2043 |
|
2122 | |||
2044 | # vi:set sr et ts=4 sw=4 ft=python : |
|
2123 | # vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent : |
@@ -13,19 +13,18 b' Developers of the IPython Notebook will need to install the following tools:' | |||||
13 |
|
13 | |||
14 | We are moving to a model where our JavaScript dependencies are managed using |
|
14 | We are moving to a model where our JavaScript dependencies are managed using | |
15 | [bower](http://bower.io/). These packages are installed in `static/components` |
|
15 | [bower](http://bower.io/). These packages are installed in `static/components` | |
16 | and committed into our git repo. Our dependencies are described in the file |
|
16 | and committed into a separate git repo [ipython/ipython-components](ipython/ipython-components). | |
|
17 | Our dependencies are described in the file | |||
17 | `static/components/bower.json`. To update our bower packages, run `fab update` |
|
18 | `static/components/bower.json`. To update our bower packages, run `fab update` | |
18 | in this directory. |
|
19 | in this directory. | |
19 |
|
20 | |||
20 | Because CodeMirror does not use proper semantic versioning for its GitHub tags, |
|
|||
21 | we maintain our own fork of CodeMirror that is used with bower. This fork should |
|
|||
22 | track the upstream CodeMirror exactly; the only difference is that we are adding |
|
|||
23 | semantic versioned tags to our repo. |
|
|||
24 |
|
||||
25 | ## less |
|
21 | ## less | |
26 |
|
22 | |||
27 | If you edit our `.less` files you will need to run the less compiler to build |
|
23 | If you edit our `.less` files you will need to run the less compiler to build | |
28 |
our minified css files. This can be done by running `fab css` from this directory |
|
24 | our minified css files. This can be done by running `fab css` from this directory, | |
|
25 | or `python setup.py css` from the root of the repository. | |||
|
26 | If you are working frequently with `.less` files please consider installing git hooks that | |||
|
27 | rebuild the css files and corresponding maps in `${RepoRoot}/git-hooks/install-hooks.sh`. | |||
29 |
|
28 | |||
30 | ## JavaScript Documentation |
|
29 | ## JavaScript Documentation | |
31 |
|
30 |
@@ -1,21 +1,7 b'' | |||||
1 | """Base Tornado handlers for the notebook. |
|
1 | """Base Tornado handlers for the notebook server.""" | |
2 |
|
||||
3 | Authors: |
|
|||
4 |
|
||||
5 | * Brian Granger |
|
|||
6 | """ |
|
|||
7 |
|
||||
8 | #----------------------------------------------------------------------------- |
|
|||
9 | # Copyright (C) 2011 The IPython Development Team |
|
|||
10 | # |
|
|||
11 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
12 | # the file COPYING, distributed as part of this software. |
|
|||
13 | #----------------------------------------------------------------------------- |
|
|||
14 |
|
||||
15 | #----------------------------------------------------------------------------- |
|
|||
16 | # Imports |
|
|||
17 | #----------------------------------------------------------------------------- |
|
|||
18 |
|
2 | |||
|
3 | # Copyright (c) IPython Development Team. | |||
|
4 | # Distributed under the terms of the Modified BSD License. | |||
19 |
|
5 | |||
20 | import functools |
|
6 | import functools | |
21 | import json |
|
7 | import json | |
@@ -41,7 +27,7 b' except ImportError:' | |||||
41 | from IPython.config import Application |
|
27 | from IPython.config import Application | |
42 | from IPython.utils.path import filefind |
|
28 | from IPython.utils.path import filefind | |
43 | from IPython.utils.py3compat import string_types |
|
29 | from IPython.utils.py3compat import string_types | |
44 | from IPython.html.utils import is_hidden |
|
30 | from IPython.html.utils import is_hidden, url_path_join, url_escape | |
45 |
|
31 | |||
46 | #----------------------------------------------------------------------------- |
|
32 | #----------------------------------------------------------------------------- | |
47 | # Top-level handlers |
|
33 | # Top-level handlers | |
@@ -53,6 +39,10 b' class AuthenticatedHandler(web.RequestHandler):' | |||||
53 |
|
39 | |||
54 | def set_default_headers(self): |
|
40 | def set_default_headers(self): | |
55 | headers = self.settings.get('headers', {}) |
|
41 | headers = self.settings.get('headers', {}) | |
|
42 | ||||
|
43 | if "X-Frame-Options" not in headers: | |||
|
44 | headers["X-Frame-Options"] = "SAMEORIGIN" | |||
|
45 | ||||
56 | for header_name,value in headers.items() : |
|
46 | for header_name,value in headers.items() : | |
57 | try: |
|
47 | try: | |
58 | self.set_header(header_name, value) |
|
48 | self.set_header(header_name, value) | |
@@ -137,6 +127,10 b' class IPythonHandler(AuthenticatedHandler):' | |||||
137 | @property |
|
127 | @property | |
138 | def base_url(self): |
|
128 | def base_url(self): | |
139 | return self.settings.get('base_url', '/') |
|
129 | return self.settings.get('base_url', '/') | |
|
130 | ||||
|
131 | @property | |||
|
132 | def ws_url(self): | |||
|
133 | return self.settings.get('websocket_url', '') | |||
140 |
|
134 | |||
141 | #--------------------------------------------------------------- |
|
135 | #--------------------------------------------------------------- | |
142 | # Manager objects |
|
136 | # Manager objects | |
@@ -147,8 +141,8 b' class IPythonHandler(AuthenticatedHandler):' | |||||
147 | return self.settings['kernel_manager'] |
|
141 | return self.settings['kernel_manager'] | |
148 |
|
142 | |||
149 | @property |
|
143 | @property | |
150 |
def |
|
144 | def contents_manager(self): | |
151 |
return self.settings[' |
|
145 | return self.settings['contents_manager'] | |
152 |
|
146 | |||
153 | @property |
|
147 | @property | |
154 | def cluster_manager(self): |
|
148 | def cluster_manager(self): | |
@@ -162,9 +156,47 b' class IPythonHandler(AuthenticatedHandler):' | |||||
162 | def kernel_spec_manager(self): |
|
156 | def kernel_spec_manager(self): | |
163 | return self.settings['kernel_spec_manager'] |
|
157 | return self.settings['kernel_spec_manager'] | |
164 |
|
158 | |||
|
159 | #--------------------------------------------------------------- | |||
|
160 | # CORS | |||
|
161 | #--------------------------------------------------------------- | |||
|
162 | ||||
165 | @property |
|
163 | @property | |
166 |
def |
|
164 | def allow_origin(self): | |
167 | return self.notebook_manager.notebook_dir |
|
165 | """Normal Access-Control-Allow-Origin""" | |
|
166 | return self.settings.get('allow_origin', '') | |||
|
167 | ||||
|
168 | @property | |||
|
169 | def allow_origin_pat(self): | |||
|
170 | """Regular expression version of allow_origin""" | |||
|
171 | return self.settings.get('allow_origin_pat', None) | |||
|
172 | ||||
|
173 | @property | |||
|
174 | def allow_credentials(self): | |||
|
175 | """Whether to set Access-Control-Allow-Credentials""" | |||
|
176 | return self.settings.get('allow_credentials', False) | |||
|
177 | ||||
|
178 | def set_default_headers(self): | |||
|
179 | """Add CORS headers, if defined""" | |||
|
180 | super(IPythonHandler, self).set_default_headers() | |||
|
181 | if self.allow_origin: | |||
|
182 | self.set_header("Access-Control-Allow-Origin", self.allow_origin) | |||
|
183 | elif self.allow_origin_pat: | |||
|
184 | origin = self.get_origin() | |||
|
185 | if origin and self.allow_origin_pat.match(origin): | |||
|
186 | self.set_header("Access-Control-Allow-Origin", origin) | |||
|
187 | if self.allow_credentials: | |||
|
188 | self.set_header("Access-Control-Allow-Credentials", 'true') | |||
|
189 | ||||
|
190 | def get_origin(self): | |||
|
191 | # Handle WebSocket Origin naming convention differences | |||
|
192 | # The difference between version 8 and 13 is that in 8 the | |||
|
193 | # client sends a "Sec-Websocket-Origin" header and in 13 it's | |||
|
194 | # simply "Origin". | |||
|
195 | if "Origin" in self.request.headers: | |||
|
196 | origin = self.request.headers.get("Origin") | |||
|
197 | else: | |||
|
198 | origin = self.request.headers.get("Sec-Websocket-Origin", None) | |||
|
199 | return origin | |||
168 |
|
200 | |||
169 | #--------------------------------------------------------------- |
|
201 | #--------------------------------------------------------------- | |
170 | # template rendering |
|
202 | # template rendering | |
@@ -183,6 +215,7 b' class IPythonHandler(AuthenticatedHandler):' | |||||
183 | def template_namespace(self): |
|
215 | def template_namespace(self): | |
184 | return dict( |
|
216 | return dict( | |
185 | base_url=self.base_url, |
|
217 | base_url=self.base_url, | |
|
218 | ws_url=self.ws_url, | |||
186 | logged_in=self.logged_in, |
|
219 | logged_in=self.logged_in, | |
187 | login_available=self.login_available, |
|
220 | login_available=self.login_available, | |
188 | static_url=self.static_url, |
|
221 | static_url=self.static_url, | |
@@ -202,12 +235,13 b' class IPythonHandler(AuthenticatedHandler):' | |||||
202 | raise web.HTTPError(400, u'Invalid JSON in body of request') |
|
235 | raise web.HTTPError(400, u'Invalid JSON in body of request') | |
203 | return model |
|
236 | return model | |
204 |
|
237 | |||
205 |
def |
|
238 | def write_error(self, status_code, **kwargs): | |
206 | """render custom error pages""" |
|
239 | """render custom error pages""" | |
207 |
exc |
|
240 | exc_info = kwargs.get('exc_info') | |
208 | message = '' |
|
241 | message = '' | |
209 | status_message = responses.get(status_code, 'Unknown HTTP Error') |
|
242 | status_message = responses.get(status_code, 'Unknown HTTP Error') | |
210 |
if exc |
|
243 | if exc_info: | |
|
244 | exception = exc_info[1] | |||
211 | # get the custom message, if defined |
|
245 | # get the custom message, if defined | |
212 | try: |
|
246 | try: | |
213 | message = exception.log_message % exception.args |
|
247 | message = exception.log_message % exception.args | |
@@ -227,13 +261,16 b' class IPythonHandler(AuthenticatedHandler):' | |||||
227 | exception=exception, |
|
261 | exception=exception, | |
228 | ) |
|
262 | ) | |
229 |
|
263 | |||
|
264 | self.set_header('Content-Type', 'text/html') | |||
230 | # render the template |
|
265 | # render the template | |
231 | try: |
|
266 | try: | |
232 | html = self.render_template('%s.html' % status_code, **ns) |
|
267 | html = self.render_template('%s.html' % status_code, **ns) | |
233 | except TemplateNotFound: |
|
268 | except TemplateNotFound: | |
234 | self.log.debug("No template for %d", status_code) |
|
269 | self.log.debug("No template for %d", status_code) | |
235 | html = self.render_template('error.html', **ns) |
|
270 | html = self.render_template('error.html', **ns) | |
236 | return html |
|
271 | ||
|
272 | self.write(html) | |||
|
273 | ||||
237 |
|
274 | |||
238 |
|
275 | |||
239 | class Template404(IPythonHandler): |
|
276 | class Template404(IPythonHandler): | |
@@ -372,6 +409,37 b' class TrailingSlashHandler(web.RequestHandler):' | |||||
372 | def get(self): |
|
409 | def get(self): | |
373 | self.redirect(self.request.uri.rstrip('/')) |
|
410 | self.redirect(self.request.uri.rstrip('/')) | |
374 |
|
411 | |||
|
412 | ||||
|
413 | class FilesRedirectHandler(IPythonHandler): | |||
|
414 | """Handler for redirecting relative URLs to the /files/ handler""" | |||
|
415 | def get(self, path=''): | |||
|
416 | cm = self.contents_manager | |||
|
417 | if cm.path_exists(path): | |||
|
418 | # it's a *directory*, redirect to /tree | |||
|
419 | url = url_path_join(self.base_url, 'tree', path) | |||
|
420 | else: | |||
|
421 | orig_path = path | |||
|
422 | # otherwise, redirect to /files | |||
|
423 | parts = path.split('/') | |||
|
424 | path = '/'.join(parts[:-1]) | |||
|
425 | name = parts[-1] | |||
|
426 | ||||
|
427 | if not cm.file_exists(name=name, path=path) and 'files' in parts: | |||
|
428 | # redirect without files/ iff it would 404 | |||
|
429 | # this preserves pre-2.0-style 'files/' links | |||
|
430 | self.log.warn("Deprecated files/ URL: %s", orig_path) | |||
|
431 | parts.remove('files') | |||
|
432 | path = '/'.join(parts[:-1]) | |||
|
433 | ||||
|
434 | if not cm.file_exists(name=name, path=path): | |||
|
435 | raise web.HTTPError(404) | |||
|
436 | ||||
|
437 | url = url_path_join(self.base_url, 'files', path, name) | |||
|
438 | url = url_escape(url) | |||
|
439 | self.log.debug("Redirecting %s to %s", self.request.path, url) | |||
|
440 | self.redirect(url) | |||
|
441 | ||||
|
442 | ||||
375 | #----------------------------------------------------------------------------- |
|
443 | #----------------------------------------------------------------------------- | |
376 | # URL pattern fragments for re-use |
|
444 | # URL pattern fragments for re-use | |
377 | #----------------------------------------------------------------------------- |
|
445 | #----------------------------------------------------------------------------- | |
@@ -379,6 +447,8 b' class TrailingSlashHandler(web.RequestHandler):' | |||||
379 | path_regex = r"(?P<path>(?:/.*)*)" |
|
447 | path_regex = r"(?P<path>(?:/.*)*)" | |
380 | notebook_name_regex = r"(?P<name>[^/]+\.ipynb)" |
|
448 | notebook_name_regex = r"(?P<name>[^/]+\.ipynb)" | |
381 | notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex) |
|
449 | notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex) | |
|
450 | file_name_regex = r"(?P<name>[^/]+)" | |||
|
451 | file_path_regex = "%s/%s" % (path_regex, file_name_regex) | |||
382 |
|
452 | |||
383 | #----------------------------------------------------------------------------- |
|
453 | #----------------------------------------------------------------------------- | |
384 | # URL to handler mappings |
|
454 | # URL to handler mappings |
@@ -15,6 +15,9 b' try:' | |||||
15 | except ImportError: |
|
15 | except ImportError: | |
16 | from Cookie import SimpleCookie # Py 2 |
|
16 | from Cookie import SimpleCookie # Py 2 | |
17 | import logging |
|
17 | import logging | |
|
18 | ||||
|
19 | import tornado | |||
|
20 | from tornado import ioloop | |||
18 | from tornado import web |
|
21 | from tornado import web | |
19 | from tornado import websocket |
|
22 | from tornado import websocket | |
20 |
|
23 | |||
@@ -26,29 +29,36 b' from .handlers import IPythonHandler' | |||||
26 |
|
29 | |||
27 |
|
30 | |||
28 | class ZMQStreamHandler(websocket.WebSocketHandler): |
|
31 | class ZMQStreamHandler(websocket.WebSocketHandler): | |
29 |
|
32 | |||
30 |
def |
|
33 | def check_origin(self, origin): | |
31 | """Check to see that origin and host match in the headers.""" |
|
34 | """Check Origin == Host or Access-Control-Allow-Origin. | |
32 |
|
35 | |||
33 | # The difference between version 8 and 13 is that in 8 the |
|
36 | Tornado >= 4 calls this method automatically, raising 403 if it returns False. | |
34 | # client sends a "Sec-Websocket-Origin" header and in 13 it's |
|
37 | We call it explicitly in `open` on Tornado < 4. | |
35 | # simply "Origin". |
|
38 | """ | |
36 | if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"): |
|
39 | if self.allow_origin == '*': | |
37 | origin_header = self.request.headers.get("Sec-Websocket-Origin") |
|
40 | return True | |
38 | else: |
|
|||
39 | origin_header = self.request.headers.get("Origin") |
|
|||
40 |
|
41 | |||
41 | host = self.request.headers.get("Host") |
|
42 | host = self.request.headers.get("Host") | |
42 |
|
43 | |||
43 | # If no header is provided, assume we can't verify origin |
|
44 | # If no header is provided, assume we can't verify origin | |
44 |
if(origin |
|
45 | if(origin is None or host is None): | |
|
46 | return False | |||
|
47 | ||||
|
48 | host_origin = "{0}://{1}".format(self.request.protocol, host) | |||
|
49 | ||||
|
50 | # OK if origin matches host | |||
|
51 | if origin == host_origin: | |||
|
52 | return True | |||
|
53 | ||||
|
54 | # Check CORS headers | |||
|
55 | if self.allow_origin: | |||
|
56 | return self.allow_origin == origin | |||
|
57 | elif self.allow_origin_pat: | |||
|
58 | return bool(self.allow_origin_pat.match(origin)) | |||
|
59 | else: | |||
|
60 | # No CORS headers deny the request | |||
45 | return False |
|
61 | return False | |
46 |
|
||||
47 | parsed_origin = urlparse(origin_header) |
|
|||
48 | origin = parsed_origin.netloc |
|
|||
49 |
|
||||
50 | # Check to see that origin matches host directly, including ports |
|
|||
51 | return origin == host |
|
|||
52 |
|
62 | |||
53 | def clear_cookie(self, *args, **kwargs): |
|
63 | def clear_cookie(self, *args, **kwargs): | |
54 | """meaningless for websockets""" |
|
64 | """meaningless for websockets""" | |
@@ -94,19 +104,41 b' class ZMQStreamHandler(websocket.WebSocketHandler):' | |||||
94 | """ |
|
104 | """ | |
95 | return True |
|
105 | return True | |
96 |
|
106 | |||
|
107 | # ping interval for keeping websockets alive (30 seconds) | |||
|
108 | WS_PING_INTERVAL = 30000 | |||
97 |
|
109 | |||
98 | class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): |
|
110 | class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): | |
|
111 | ping_callback = None | |||
|
112 | ||||
|
113 | def set_default_headers(self): | |||
|
114 | """Undo the set_default_headers in IPythonHandler | |||
|
115 | ||||
|
116 | which doesn't make sense for websockets | |||
|
117 | """ | |||
|
118 | pass | |||
99 |
|
119 | |||
100 | def open(self, kernel_id): |
|
120 | def open(self, kernel_id): | |
101 | self.kernel_id = cast_unicode(kernel_id, 'ascii') |
|
121 | self.kernel_id = cast_unicode(kernel_id, 'ascii') | |
102 | # Check to see that origin matches host directly, including ports |
|
122 | # Check to see that origin matches host directly, including ports | |
103 | if not self.same_origin(): |
|
123 | # Tornado 4 already does CORS checking | |
104 | self.log.warn("Cross Origin WebSocket Attempt.") |
|
124 | if tornado.version_info[0] < 4: | |
105 | raise web.HTTPError(404) |
|
125 | if not self.check_origin(self.get_origin()): | |
|
126 | self.log.warn("Cross Origin WebSocket Attempt from %s", self.get_origin()) | |||
|
127 | raise web.HTTPError(403) | |||
106 |
|
128 | |||
107 | self.session = Session(config=self.config) |
|
129 | self.session = Session(config=self.config) | |
108 | self.save_on_message = self.on_message |
|
130 | self.save_on_message = self.on_message | |
109 | self.on_message = self.on_first_message |
|
131 | self.on_message = self.on_first_message | |
|
132 | self.ping_callback = ioloop.PeriodicCallback(self.send_ping, WS_PING_INTERVAL) | |||
|
133 | self.ping_callback.start() | |||
|
134 | ||||
|
135 | def send_ping(self): | |||
|
136 | """send a ping to keep the websocket alive""" | |||
|
137 | if self.stream.closed() and self.ping_callback is not None: | |||
|
138 | self.ping_callback.stop() | |||
|
139 | return | |||
|
140 | ||||
|
141 | self.ping(b'') | |||
110 |
|
142 | |||
111 | def _inject_cookie_message(self, msg): |
|
143 | def _inject_cookie_message(self, msg): | |
112 | """Inject the first message, which is the document cookie, |
|
144 | """Inject the first message, which is the document cookie, |
@@ -8,31 +8,65 b' from subprocess import check_output' | |||||
8 |
|
8 | |||
9 | pjoin = os.path.join |
|
9 | pjoin = os.path.join | |
10 | static_dir = 'static' |
|
10 | static_dir = 'static' | |
11 |
components_dir = |
|
11 | components_dir = pjoin(static_dir, 'components') | |
|
12 | here = os.path.dirname(__file__) | |||
12 |
|
13 | |||
13 |
min_less_version = '1. |
|
14 | min_less_version = '1.7.0' | |
14 |
max_less_version = '1. |
|
15 | max_less_version = '1.8.0' # exclusive | |
15 |
|
16 | |||
16 | def css(minify=True, verbose=False): |
|
17 | def _need_css_update(): | |
|
18 | """Does less need to run?""" | |||
|
19 | ||||
|
20 | static_path = pjoin(here, static_dir) | |||
|
21 | css_targets = [ | |||
|
22 | pjoin(static_path, 'style', '%s.min.css' % name) | |||
|
23 | for name in ('style', 'ipython') | |||
|
24 | ] | |||
|
25 | css_maps = [t + '.map' for t in css_targets] | |||
|
26 | targets = css_targets + css_maps | |||
|
27 | if not all(os.path.exists(t) for t in targets): | |||
|
28 | # some generated files don't exist | |||
|
29 | return True | |||
|
30 | earliest_target = sorted(os.stat(t).st_mtime for t in targets)[0] | |||
|
31 | ||||
|
32 | # check if any .less files are newer than the generated targets | |||
|
33 | for (dirpath, dirnames, filenames) in os.walk(static_path): | |||
|
34 | for f in filenames: | |||
|
35 | if f.endswith('.less'): | |||
|
36 | path = pjoin(static_path, dirpath, f) | |||
|
37 | timestamp = os.stat(path).st_mtime | |||
|
38 | if timestamp > earliest_target: | |||
|
39 | return True | |||
|
40 | ||||
|
41 | return False | |||
|
42 | ||||
|
43 | def css(minify=False, verbose=False, force=False): | |||
17 | """generate the css from less files""" |
|
44 | """generate the css from less files""" | |
|
45 | minify = _to_bool(minify) | |||
|
46 | verbose = _to_bool(verbose) | |||
|
47 | force = _to_bool(force) | |||
|
48 | # minify implies force because it's not the default behavior | |||
|
49 | if not force and not minify and not _need_css_update(): | |||
|
50 | print("css up-to-date") | |||
|
51 | return | |||
|
52 | ||||
18 | for name in ('style', 'ipython'): |
|
53 | for name in ('style', 'ipython'): | |
19 | source = pjoin('style', "%s.less" % name) |
|
54 | source = pjoin('style', "%s.less" % name) | |
20 | target = pjoin('style', "%s.min.css" % name) |
|
55 | target = pjoin('style', "%s.min.css" % name) | |
21 | _compile_less(source, target, minify, verbose) |
|
56 | sourcemap = pjoin('style', "%s.min.css.map" % name) | |
|
57 | _compile_less(source, target, sourcemap, minify, verbose) | |||
22 |
|
58 | |||
23 | def _to_bool(b): |
|
59 | def _to_bool(b): | |
24 | if not b in ['True', 'False', True, False]: |
|
60 | if not b in ['True', 'False', True, False]: | |
25 | abort('boolean expected, got: %s' % b) |
|
61 | abort('boolean expected, got: %s' % b) | |
26 | return (b in ['True', True]) |
|
62 | return (b in ['True', True]) | |
27 |
|
63 | |||
28 | def _compile_less(source, target, minify=True, verbose=False): |
|
64 | def _compile_less(source, target, sourcemap, minify=True, verbose=False): | |
29 | """Compile a less file by source and target relative to static_dir""" |
|
65 | """Compile a less file by source and target relative to static_dir""" | |
30 | minify = _to_bool(minify) |
|
|||
31 | verbose = _to_bool(verbose) |
|
|||
32 | min_flag = '-x' if minify is True else '' |
|
66 | min_flag = '-x' if minify is True else '' | |
33 | ver_flag = '--verbose' if verbose is True else '' |
|
67 | ver_flag = '--verbose' if verbose is True else '' | |
34 |
|
68 | |||
35 | # pin less to 1.4 |
|
69 | # pin less to version number from above | |
36 | try: |
|
70 | try: | |
37 | out = check_output(['lessc', '--version']) |
|
71 | out = check_output(['lessc', '--version']) | |
38 | except OSError as err: |
|
72 | except OSError as err: | |
@@ -45,6 +79,7 b' def _compile_less(source, target, minify=True, verbose=False):' | |||||
45 | if V(less_version) >= V(max_less_version): |
|
79 | if V(less_version) >= V(max_less_version): | |
46 | raise ValueError("lessc too new: %s >= %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, max_less_version)) |
|
80 | raise ValueError("lessc too new: %s >= %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, max_less_version)) | |
47 |
|
81 | |||
|
82 | static_path = pjoin(here, static_dir) | |||
48 | with lcd(static_dir): |
|
83 | with lcd(static_dir): | |
49 | local('lessc {min_flag} {ver_flag} {source} {target}'.format(**locals())) |
|
84 | local('lessc {min_flag} {ver_flag} --source-map={sourcemap} --source-map-basepath={static_path} --source-map-rootpath="../" {source} {target}'.format(**locals())) | |
50 |
|
85 |
@@ -1,10 +1,18 b'' | |||||
|
1 | """Tornado handlers for nbconvert.""" | |||
|
2 | ||||
|
3 | # Copyright (c) IPython Development Team. | |||
|
4 | # Distributed under the terms of the Modified BSD License. | |||
|
5 | ||||
1 | import io |
|
6 | import io | |
2 | import os |
|
7 | import os | |
3 | import zipfile |
|
8 | import zipfile | |
4 |
|
9 | |||
5 | from tornado import web |
|
10 | from tornado import web | |
6 |
|
11 | |||
7 |
from ..base.handlers import |
|
12 | from ..base.handlers import ( | |
|
13 | IPythonHandler, FilesRedirectHandler, | |||
|
14 | notebook_path_regex, path_regex, | |||
|
15 | ) | |||
8 | from IPython.nbformat.current import to_notebook_json |
|
16 | from IPython.nbformat.current import to_notebook_json | |
9 |
|
17 | |||
10 | from IPython.utils.py3compat import cast_bytes |
|
18 | from IPython.utils.py3compat import cast_bytes | |
@@ -73,7 +81,7 b' class NbconvertFileHandler(IPythonHandler):' | |||||
73 | exporter = get_exporter(format, config=self.config, log=self.log) |
|
81 | exporter = get_exporter(format, config=self.config, log=self.log) | |
74 |
|
82 | |||
75 | path = path.strip('/') |
|
83 | path = path.strip('/') | |
76 |
model = self. |
|
84 | model = self.contents_manager.get_model(name=name, path=path) | |
77 |
|
85 | |||
78 | self.set_header('Last-Modified', model['last_modified']) |
|
86 | self.set_header('Last-Modified', model['last_modified']) | |
79 |
|
87 | |||
@@ -123,6 +131,7 b' class NbconvertPostHandler(IPythonHandler):' | |||||
123 |
|
131 | |||
124 | self.finish(output) |
|
132 | self.finish(output) | |
125 |
|
133 | |||
|
134 | ||||
126 | #----------------------------------------------------------------------------- |
|
135 | #----------------------------------------------------------------------------- | |
127 | # URL to handler mappings |
|
136 | # URL to handler mappings | |
128 | #----------------------------------------------------------------------------- |
|
137 | #----------------------------------------------------------------------------- | |
@@ -134,4 +143,5 b' default_handlers = [' | |||||
134 | (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex), |
|
143 | (r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex), | |
135 | NbconvertFileHandler), |
|
144 | NbconvertFileHandler), | |
136 | (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler), |
|
145 | (r"/nbconvert/%s" % _format_regex, NbconvertPostHandler), | |
|
146 | (r"/nbconvert/html%s" % path_regex, FilesRedirectHandler), | |||
137 | ] |
|
147 | ] |
@@ -106,7 +106,7 b' class APITest(NotebookTestBase):' | |||||
106 |
|
106 | |||
107 | @onlyif_cmds_exist('pandoc') |
|
107 | @onlyif_cmds_exist('pandoc') | |
108 | def test_from_post(self): |
|
108 | def test_from_post(self): | |
109 |
nbmodel_url = url_path_join(self.base_url(), 'api/ |
|
109 | nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb') | |
110 | nbmodel = requests.get(nbmodel_url).json() |
|
110 | nbmodel = requests.get(nbmodel_url).json() | |
111 |
|
111 | |||
112 | r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel) |
|
112 | r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel) | |
@@ -121,7 +121,7 b' class APITest(NotebookTestBase):' | |||||
121 |
|
121 | |||
122 | @onlyif_cmds_exist('pandoc') |
|
122 | @onlyif_cmds_exist('pandoc') | |
123 | def test_from_post_zip(self): |
|
123 | def test_from_post_zip(self): | |
124 |
nbmodel_url = url_path_join(self.base_url(), 'api/ |
|
124 | nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb') | |
125 | nbmodel = requests.get(nbmodel_url).json() |
|
125 | nbmodel = requests.get(nbmodel_url).json() | |
126 |
|
126 | |||
127 | r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel) |
|
127 | r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel) |
@@ -1,31 +1,17 b'' | |||||
1 | """Tornado handlers for the live notebook view. |
|
1 | """Tornado handlers for the live notebook view.""" | |
2 |
|
2 | |||
3 | Authors: |
|
3 | # Copyright (c) IPython Development Team. | |
4 |
|
4 | # Distributed under the terms of the Modified BSD License. | ||
5 | * Brian Granger |
|
|||
6 | """ |
|
|||
7 |
|
||||
8 | #----------------------------------------------------------------------------- |
|
|||
9 | # Copyright (C) 2011 The IPython Development Team |
|
|||
10 | # |
|
|||
11 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
12 | # the file COPYING, distributed as part of this software. |
|
|||
13 | #----------------------------------------------------------------------------- |
|
|||
14 |
|
||||
15 | #----------------------------------------------------------------------------- |
|
|||
16 | # Imports |
|
|||
17 | #----------------------------------------------------------------------------- |
|
|||
18 |
|
5 | |||
19 | import os |
|
6 | import os | |
20 | from tornado import web |
|
7 | from tornado import web | |
21 | HTTPError = web.HTTPError |
|
8 | HTTPError = web.HTTPError | |
22 |
|
9 | |||
23 | from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex |
|
10 | from ..base.handlers import ( | |
24 | from ..utils import url_path_join, url_escape |
|
11 | IPythonHandler, FilesRedirectHandler, | |
25 |
|
12 | notebook_path_regex, path_regex, | ||
26 | #----------------------------------------------------------------------------- |
|
13 | ) | |
27 | # Handlers |
|
14 | from ..utils import url_escape | |
28 | #----------------------------------------------------------------------------- |
|
|||
29 |
|
15 | |||
30 |
|
16 | |||
31 | class NotebookHandler(IPythonHandler): |
|
17 | class NotebookHandler(IPythonHandler): | |
@@ -35,17 +21,16 b' class NotebookHandler(IPythonHandler):' | |||||
35 | """get renders the notebook template if a name is given, or |
|
21 | """get renders the notebook template if a name is given, or | |
36 | redirects to the '/files/' handler if the name is not given.""" |
|
22 | redirects to the '/files/' handler if the name is not given.""" | |
37 | path = path.strip('/') |
|
23 | path = path.strip('/') | |
38 |
|
|
24 | cm = self.contents_manager | |
39 | if name is None: |
|
25 | if name is None: | |
40 | raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri) |
|
26 | raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri) | |
41 |
|
27 | |||
42 | # a .ipynb filename was given |
|
28 | # a .ipynb filename was given | |
43 |
if not |
|
29 | if not cm.file_exists(name, path): | |
44 | raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name)) |
|
30 | raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name)) | |
45 | name = url_escape(name) |
|
31 | name = url_escape(name) | |
46 | path = url_escape(path) |
|
32 | path = url_escape(path) | |
47 | self.write(self.render_template('notebook.html', |
|
33 | self.write(self.render_template('notebook.html', | |
48 | project=self.project_dir, |
|
|||
49 | notebook_path=path, |
|
34 | notebook_path=path, | |
50 | notebook_name=name, |
|
35 | notebook_name=name, | |
51 | kill_kernel=False, |
|
36 | kill_kernel=False, | |
@@ -53,30 +38,6 b' class NotebookHandler(IPythonHandler):' | |||||
53 | ) |
|
38 | ) | |
54 | ) |
|
39 | ) | |
55 |
|
40 | |||
56 | class NotebookRedirectHandler(IPythonHandler): |
|
|||
57 | def get(self, path=''): |
|
|||
58 | nbm = self.notebook_manager |
|
|||
59 | if nbm.path_exists(path): |
|
|||
60 | # it's a *directory*, redirect to /tree |
|
|||
61 | url = url_path_join(self.base_url, 'tree', path) |
|
|||
62 | else: |
|
|||
63 | # otherwise, redirect to /files |
|
|||
64 | if '/files/' in path: |
|
|||
65 | # redirect without files/ iff it would 404 |
|
|||
66 | # this preserves pre-2.0-style 'files/' links |
|
|||
67 | # FIXME: this is hardcoded based on notebook_path, |
|
|||
68 | # but so is the files handler itself, |
|
|||
69 | # so it should work until both are cleaned up. |
|
|||
70 | parts = path.split('/') |
|
|||
71 | files_path = os.path.join(nbm.notebook_dir, *parts) |
|
|||
72 | if not os.path.exists(files_path): |
|
|||
73 | self.log.warn("Deprecated files/ URL: %s", path) |
|
|||
74 | path = path.replace('/files/', '/', 1) |
|
|||
75 |
|
||||
76 | url = url_path_join(self.base_url, 'files', path) |
|
|||
77 | url = url_escape(url) |
|
|||
78 | self.log.debug("Redirecting %s to %s", self.request.path, url) |
|
|||
79 | self.redirect(url) |
|
|||
80 |
|
41 | |||
81 | #----------------------------------------------------------------------------- |
|
42 | #----------------------------------------------------------------------------- | |
82 | # URL to handler mappings |
|
43 | # URL to handler mappings | |
@@ -85,6 +46,6 b' class NotebookRedirectHandler(IPythonHandler):' | |||||
85 |
|
46 | |||
86 | default_handlers = [ |
|
47 | default_handlers = [ | |
87 | (r"/notebooks%s" % notebook_path_regex, NotebookHandler), |
|
48 | (r"/notebooks%s" % notebook_path_regex, NotebookHandler), | |
88 |
(r"/notebooks%s" % path_regex, |
|
49 | (r"/notebooks%s" % path_regex, FilesRedirectHandler), | |
89 | ] |
|
50 | ] | |
90 |
|
51 |
@@ -6,12 +6,14 b'' | |||||
6 |
|
6 | |||
7 | from __future__ import print_function |
|
7 | from __future__ import print_function | |
8 |
|
8 | |||
|
9 | import base64 | |||
9 | import errno |
|
10 | import errno | |
10 | import io |
|
11 | import io | |
11 | import json |
|
12 | import json | |
12 | import logging |
|
13 | import logging | |
13 | import os |
|
14 | import os | |
14 | import random |
|
15 | import random | |
|
16 | import re | |||
15 | import select |
|
17 | import select | |
16 | import signal |
|
18 | import signal | |
17 | import socket |
|
19 | import socket | |
@@ -53,8 +55,8 b' from IPython.html import DEFAULT_STATIC_FILES_PATH' | |||||
53 | from .base.handlers import Template404 |
|
55 | from .base.handlers import Template404 | |
54 | from .log import log_request |
|
56 | from .log import log_request | |
55 | from .services.kernels.kernelmanager import MappingKernelManager |
|
57 | from .services.kernels.kernelmanager import MappingKernelManager | |
56 |
from .services. |
|
58 | from .services.contents.manager import ContentsManager | |
57 |
from .services. |
|
59 | from .services.contents.filemanager import FileContentsManager | |
58 | from .services.clusters.clustermanager import ClusterManager |
|
60 | from .services.clusters.clustermanager import ClusterManager | |
59 | from .services.sessions.sessionmanager import SessionManager |
|
61 | from .services.sessions.sessionmanager import SessionManager | |
60 |
|
62 | |||
@@ -72,6 +74,7 b' from IPython.kernel.zmq.session import default_secure, Session' | |||||
72 | from IPython.nbformat.sign import NotebookNotary |
|
74 | from IPython.nbformat.sign import NotebookNotary | |
73 | from IPython.utils.importstring import import_item |
|
75 | from IPython.utils.importstring import import_item | |
74 | from IPython.utils import submodule |
|
76 | from IPython.utils import submodule | |
|
77 | from IPython.utils.process import check_pid | |||
75 | from IPython.utils.traitlets import ( |
|
78 | from IPython.utils.traitlets import ( | |
76 | Dict, Unicode, Integer, List, Bool, Bytes, Instance, |
|
79 | Dict, Unicode, Integer, List, Bool, Bytes, Instance, | |
77 | DottedObjectName, TraitError, |
|
80 | DottedObjectName, TraitError, | |
@@ -118,19 +121,19 b' def load_handlers(name):' | |||||
118 |
|
121 | |||
119 | class NotebookWebApplication(web.Application): |
|
122 | class NotebookWebApplication(web.Application): | |
120 |
|
123 | |||
121 |
def __init__(self, ipython_app, kernel_manager, |
|
124 | def __init__(self, ipython_app, kernel_manager, contents_manager, | |
122 | cluster_manager, session_manager, kernel_spec_manager, log, |
|
125 | cluster_manager, session_manager, kernel_spec_manager, log, | |
123 | base_url, settings_overrides, jinja_env_options): |
|
126 | base_url, settings_overrides, jinja_env_options): | |
124 |
|
127 | |||
125 | settings = self.init_settings( |
|
128 | settings = self.init_settings( | |
126 |
ipython_app, kernel_manager, |
|
129 | ipython_app, kernel_manager, contents_manager, cluster_manager, | |
127 | session_manager, kernel_spec_manager, log, base_url, |
|
130 | session_manager, kernel_spec_manager, log, base_url, | |
128 | settings_overrides, jinja_env_options) |
|
131 | settings_overrides, jinja_env_options) | |
129 | handlers = self.init_handlers(settings) |
|
132 | handlers = self.init_handlers(settings) | |
130 |
|
133 | |||
131 | super(NotebookWebApplication, self).__init__(handlers, **settings) |
|
134 | super(NotebookWebApplication, self).__init__(handlers, **settings) | |
132 |
|
135 | |||
133 |
def init_settings(self, ipython_app, kernel_manager, |
|
136 | def init_settings(self, ipython_app, kernel_manager, contents_manager, | |
134 | cluster_manager, session_manager, kernel_spec_manager, |
|
137 | cluster_manager, session_manager, kernel_spec_manager, | |
135 | log, base_url, settings_overrides, |
|
138 | log, base_url, settings_overrides, | |
136 | jinja_env_options=None): |
|
139 | jinja_env_options=None): | |
@@ -162,13 +165,14 b' class NotebookWebApplication(web.Application):' | |||||
162 |
|
165 | |||
163 | # managers |
|
166 | # managers | |
164 | kernel_manager=kernel_manager, |
|
167 | kernel_manager=kernel_manager, | |
165 |
|
|
168 | contents_manager=contents_manager, | |
166 | cluster_manager=cluster_manager, |
|
169 | cluster_manager=cluster_manager, | |
167 | session_manager=session_manager, |
|
170 | session_manager=session_manager, | |
168 | kernel_spec_manager=kernel_spec_manager, |
|
171 | kernel_spec_manager=kernel_spec_manager, | |
169 |
|
172 | |||
170 | # IPython stuff |
|
173 | # IPython stuff | |
171 | nbextensions_path = ipython_app.nbextensions_path, |
|
174 | nbextensions_path = ipython_app.nbextensions_path, | |
|
175 | websocket_url=ipython_app.websocket_url, | |||
172 | mathjax_url=ipython_app.mathjax_url, |
|
176 | mathjax_url=ipython_app.mathjax_url, | |
173 | config=ipython_app.config, |
|
177 | config=ipython_app.config, | |
174 | jinja2_env=env, |
|
178 | jinja2_env=env, | |
@@ -189,18 +193,20 b' class NotebookWebApplication(web.Application):' | |||||
189 | handlers.extend(load_handlers('nbconvert.handlers')) |
|
193 | handlers.extend(load_handlers('nbconvert.handlers')) | |
190 | handlers.extend(load_handlers('kernelspecs.handlers')) |
|
194 | handlers.extend(load_handlers('kernelspecs.handlers')) | |
191 | handlers.extend(load_handlers('services.kernels.handlers')) |
|
195 | handlers.extend(load_handlers('services.kernels.handlers')) | |
192 |
handlers.extend(load_handlers('services. |
|
196 | handlers.extend(load_handlers('services.contents.handlers')) | |
193 | handlers.extend(load_handlers('services.clusters.handlers')) |
|
197 | handlers.extend(load_handlers('services.clusters.handlers')) | |
194 | handlers.extend(load_handlers('services.sessions.handlers')) |
|
198 | handlers.extend(load_handlers('services.sessions.handlers')) | |
195 | handlers.extend(load_handlers('services.nbconvert.handlers')) |
|
199 | handlers.extend(load_handlers('services.nbconvert.handlers')) | |
196 | handlers.extend(load_handlers('services.kernelspecs.handlers')) |
|
200 | handlers.extend(load_handlers('services.kernelspecs.handlers')) | |
197 | # FIXME: /files/ should be handled by the Contents service when it exists |
|
201 | # FIXME: /files/ should be handled by the Contents service when it exists | |
198 |
|
|
202 | cm = settings['contents_manager'] | |
199 |
if hasattr( |
|
203 | if hasattr(cm, 'root_dir'): | |
200 |
handlers. |
|
204 | handlers.append( | |
201 |
(r"/files/(.*)", AuthenticatedFileHandler, {'path' : |
|
205 | (r"/files/(.*)", AuthenticatedFileHandler, {'path' : cm.root_dir}), | |
|
206 | ) | |||
|
207 | handlers.append( | |||
202 | (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}), |
|
208 | (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}), | |
203 |
|
|
209 | ) | |
204 | # prepend base_url onto the patterns that we match |
|
210 | # prepend base_url onto the patterns that we match | |
205 | new_handlers = [] |
|
211 | new_handlers = [] | |
206 | for handler in handlers: |
|
212 | for handler in handlers: | |
@@ -260,9 +266,9 b" flags['no-mathjax']=(" | |||||
260 | ) |
|
266 | ) | |
261 |
|
267 | |||
262 | # Add notebook manager flags |
|
268 | # Add notebook manager flags | |
263 |
flags.update(boolean_flag('script', 'File |
|
269 | flags.update(boolean_flag('script', 'FileContentsManager.save_script', | |
264 | 'Auto-save a .py script everytime the .ipynb notebook is saved', |
|
270 | 'DEPRECATED, IGNORED', | |
265 | 'Do not auto-save .py scripts for every notebook')) |
|
271 | 'DEPRECATED, IGNORED')) | |
266 |
|
272 | |||
267 | aliases = dict(base_aliases) |
|
273 | aliases = dict(base_aliases) | |
268 |
|
274 | |||
@@ -298,7 +304,7 b' class NotebookApp(BaseIPythonApplication):' | |||||
298 |
|
304 | |||
299 | classes = [ |
|
305 | classes = [ | |
300 | KernelManager, ProfileDir, Session, MappingKernelManager, |
|
306 | KernelManager, ProfileDir, Session, MappingKernelManager, | |
301 |
|
|
307 | ContentsManager, FileContentsManager, NotebookNotary, | |
302 | ] |
|
308 | ] | |
303 | flags = Dict(flags) |
|
309 | flags = Dict(flags) | |
304 | aliases = Dict(aliases) |
|
310 | aliases = Dict(aliases) | |
@@ -333,8 +339,34 b' class NotebookApp(BaseIPythonApplication):' | |||||
333 | self.file_to_run = base |
|
339 | self.file_to_run = base | |
334 | self.notebook_dir = path |
|
340 | self.notebook_dir = path | |
335 |
|
341 | |||
336 |
# Network related information |
|
342 | # Network related information | |
337 |
|
343 | |||
|
344 | allow_origin = Unicode('', config=True, | |||
|
345 | help="""Set the Access-Control-Allow-Origin header | |||
|
346 | ||||
|
347 | Use '*' to allow any origin to access your server. | |||
|
348 | ||||
|
349 | Takes precedence over allow_origin_pat. | |||
|
350 | """ | |||
|
351 | ) | |||
|
352 | ||||
|
353 | allow_origin_pat = Unicode('', config=True, | |||
|
354 | help="""Use a regular expression for the Access-Control-Allow-Origin header | |||
|
355 | ||||
|
356 | Requests from an origin matching the expression will get replies with: | |||
|
357 | ||||
|
358 | Access-Control-Allow-Origin: origin | |||
|
359 | ||||
|
360 | where `origin` is the origin of the request. | |||
|
361 | ||||
|
362 | Ignored if allow_origin is set. | |||
|
363 | """ | |||
|
364 | ) | |||
|
365 | ||||
|
366 | allow_credentials = Bool(False, config=True, | |||
|
367 | help="Set the Access-Control-Allow-Credentials: true header" | |||
|
368 | ) | |||
|
369 | ||||
338 | ip = Unicode('localhost', config=True, |
|
370 | ip = Unicode('localhost', config=True, | |
339 | help="The IP address the notebook server will listen on." |
|
371 | help="The IP address the notebook server will listen on." | |
340 | ) |
|
372 | ) | |
@@ -357,6 +389,14 b' class NotebookApp(BaseIPythonApplication):' | |||||
357 | help="""The full path to a private key file for usage with SSL/TLS.""" |
|
389 | help="""The full path to a private key file for usage with SSL/TLS.""" | |
358 | ) |
|
390 | ) | |
359 |
|
391 | |||
|
392 | cookie_secret_file = Unicode(config=True, | |||
|
393 | help="""The file where the cookie secret is stored.""" | |||
|
394 | ) | |||
|
395 | def _cookie_secret_file_default(self): | |||
|
396 | if self.profile_dir is None: | |||
|
397 | return '' | |||
|
398 | return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret') | |||
|
399 | ||||
360 | cookie_secret = Bytes(b'', config=True, |
|
400 | cookie_secret = Bytes(b'', config=True, | |
361 | help="""The random bytes used to secure cookies. |
|
401 | help="""The random bytes used to secure cookies. | |
362 | By default this is a new random number every time you start the Notebook. |
|
402 | By default this is a new random number every time you start the Notebook. | |
@@ -367,7 +407,26 b' class NotebookApp(BaseIPythonApplication):' | |||||
367 | """ |
|
407 | """ | |
368 | ) |
|
408 | ) | |
369 | def _cookie_secret_default(self): |
|
409 | def _cookie_secret_default(self): | |
370 | return os.urandom(1024) |
|
410 | if os.path.exists(self.cookie_secret_file): | |
|
411 | with io.open(self.cookie_secret_file, 'rb') as f: | |||
|
412 | return f.read() | |||
|
413 | else: | |||
|
414 | secret = base64.encodestring(os.urandom(1024)) | |||
|
415 | self._write_cookie_secret_file(secret) | |||
|
416 | return secret | |||
|
417 | ||||
|
418 | def _write_cookie_secret_file(self, secret): | |||
|
419 | """write my secret to my secret_file""" | |||
|
420 | self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file) | |||
|
421 | with io.open(self.cookie_secret_file, 'wb') as f: | |||
|
422 | f.write(secret) | |||
|
423 | try: | |||
|
424 | os.chmod(self.cookie_secret_file, 0o600) | |||
|
425 | except OSError: | |||
|
426 | self.log.warn( | |||
|
427 | "Could not set permissions on %s", | |||
|
428 | self.cookie_secret_file | |||
|
429 | ) | |||
371 |
|
430 | |||
372 | password = Unicode(u'', config=True, |
|
431 | password = Unicode(u'', config=True, | |
373 | help="""Hashed password to use for web authentication. |
|
432 | help="""Hashed password to use for web authentication. | |
@@ -456,6 +515,13 b' class NotebookApp(BaseIPythonApplication):' | |||||
456 | def _nbextensions_path_default(self): |
|
515 | def _nbextensions_path_default(self): | |
457 | return [os.path.join(get_ipython_dir(), 'nbextensions')] |
|
516 | return [os.path.join(get_ipython_dir(), 'nbextensions')] | |
458 |
|
517 | |||
|
518 | websocket_url = Unicode("", config=True, | |||
|
519 | help="""The base URL for websockets, | |||
|
520 | if it differs from the HTTP server (hint: it almost certainly doesn't). | |||
|
521 | ||||
|
522 | Should be in the form of an HTTP origin: ws[s]://hostname[:port] | |||
|
523 | """ | |||
|
524 | ) | |||
459 | mathjax_url = Unicode("", config=True, |
|
525 | mathjax_url = Unicode("", config=True, | |
460 | help="""The url for MathJax.js.""" |
|
526 | help="""The url for MathJax.js.""" | |
461 | ) |
|
527 | ) | |
@@ -482,13 +548,7 b' class NotebookApp(BaseIPythonApplication):' | |||||
482 | return url |
|
548 | return url | |
483 |
|
549 | |||
484 | # no local mathjax, serve from CDN |
|
550 | # no local mathjax, serve from CDN | |
485 | if self.certfile: |
|
551 | url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js" | |
486 | # HTTPS: load from Rackspace CDN, because SSL certificate requires it |
|
|||
487 | host = u"https://c328740.ssl.cf1.rackcdn.com" |
|
|||
488 | else: |
|
|||
489 | host = u"http://cdn.mathjax.org" |
|
|||
490 |
|
||||
491 | url = host + u"/mathjax/latest/MathJax.js" |
|
|||
492 | self.log.info("Using MathJax from CDN: %s", url) |
|
552 | self.log.info("Using MathJax from CDN: %s", url) | |
493 | return url |
|
553 | return url | |
494 |
|
554 | |||
@@ -499,7 +559,7 b' class NotebookApp(BaseIPythonApplication):' | |||||
499 | else: |
|
559 | else: | |
500 | self.log.info("Using MathJax: %s", new) |
|
560 | self.log.info("Using MathJax: %s", new) | |
501 |
|
561 | |||
502 |
|
|
562 | contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager', | |
503 | config=True, |
|
563 | config=True, | |
504 | help='The notebook manager class to use.' |
|
564 | help='The notebook manager class to use.' | |
505 | ) |
|
565 | ) | |
@@ -563,7 +623,7 b' class NotebookApp(BaseIPythonApplication):' | |||||
563 | raise TraitError("No such notebook dir: %r" % new) |
|
623 | raise TraitError("No such notebook dir: %r" % new) | |
564 |
|
624 | |||
565 | # setting App.notebook_dir implies setting notebook and kernel dirs as well |
|
625 | # setting App.notebook_dir implies setting notebook and kernel dirs as well | |
566 |
self.config.File |
|
626 | self.config.FileContentsManager.root_dir = new | |
567 | self.config.MappingKernelManager.root_dir = new |
|
627 | self.config.MappingKernelManager.root_dir = new | |
568 |
|
628 | |||
569 |
|
629 | |||
@@ -589,11 +649,8 b' class NotebookApp(BaseIPythonApplication):' | |||||
589 |
|
649 | |||
590 | def init_kernel_argv(self): |
|
650 | def init_kernel_argv(self): | |
591 | """construct the kernel arguments""" |
|
651 | """construct the kernel arguments""" | |
592 | self.kernel_argv = [] |
|
|||
593 | # Kernel should inherit default config file from frontend |
|
|||
594 | self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name) |
|
|||
595 | # Kernel should get *absolute* path to profile directory |
|
652 | # Kernel should get *absolute* path to profile directory | |
596 |
self.kernel_argv |
|
653 | self.kernel_argv = ["--profile-dir", self.profile_dir.location] | |
597 |
|
654 | |||
598 | def init_configurables(self): |
|
655 | def init_configurables(self): | |
599 | # force Session default to be secure |
|
656 | # force Session default to be secure | |
@@ -603,10 +660,12 b' class NotebookApp(BaseIPythonApplication):' | |||||
603 | parent=self, log=self.log, kernel_argv=self.kernel_argv, |
|
660 | parent=self, log=self.log, kernel_argv=self.kernel_argv, | |
604 | connection_dir = self.profile_dir.security_dir, |
|
661 | connection_dir = self.profile_dir.security_dir, | |
605 | ) |
|
662 | ) | |
606 |
kls = import_item(self. |
|
663 | kls = import_item(self.contents_manager_class) | |
607 |
self. |
|
664 | self.contents_manager = kls(parent=self, log=self.log) | |
608 | kls = import_item(self.session_manager_class) |
|
665 | kls = import_item(self.session_manager_class) | |
609 |
self.session_manager = kls(parent=self, log=self.log |
|
666 | self.session_manager = kls(parent=self, log=self.log, | |
|
667 | kernel_manager=self.kernel_manager, | |||
|
668 | contents_manager=self.contents_manager) | |||
610 | kls = import_item(self.cluster_manager_class) |
|
669 | kls = import_item(self.cluster_manager_class) | |
611 | self.cluster_manager = kls(parent=self, log=self.log) |
|
670 | self.cluster_manager = kls(parent=self, log=self.log) | |
612 | self.cluster_manager.update_profiles() |
|
671 | self.cluster_manager.update_profiles() | |
@@ -625,8 +684,13 b' class NotebookApp(BaseIPythonApplication):' | |||||
625 |
|
684 | |||
626 | def init_webapp(self): |
|
685 | def init_webapp(self): | |
627 | """initialize tornado webapp and httpserver""" |
|
686 | """initialize tornado webapp and httpserver""" | |
|
687 | self.webapp_settings['allow_origin'] = self.allow_origin | |||
|
688 | if self.allow_origin_pat: | |||
|
689 | self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat) | |||
|
690 | self.webapp_settings['allow_credentials'] = self.allow_credentials | |||
|
691 | ||||
628 | self.web_app = NotebookWebApplication( |
|
692 | self.web_app = NotebookWebApplication( | |
629 |
self, self.kernel_manager, self. |
|
693 | self, self.kernel_manager, self.contents_manager, | |
630 | self.cluster_manager, self.session_manager, self.kernel_spec_manager, |
|
694 | self.cluster_manager, self.session_manager, self.kernel_spec_manager, | |
631 | self.log, self.base_url, self.webapp_settings, |
|
695 | self.log, self.base_url, self.webapp_settings, | |
632 | self.jinja_environment_options |
|
696 | self.jinja_environment_options | |
@@ -717,8 +781,6 b' class NotebookApp(BaseIPythonApplication):' | |||||
717 |
|
781 | |||
718 | This doesn't work on Windows. |
|
782 | This doesn't work on Windows. | |
719 | """ |
|
783 | """ | |
720 | # FIXME: remove this delay when pyzmq dependency is >= 2.1.11 |
|
|||
721 | time.sleep(0.1) |
|
|||
722 | info = self.log.info |
|
784 | info = self.log.info | |
723 | info('interrupted') |
|
785 | info('interrupted') | |
724 | print(self.notebook_info()) |
|
786 | print(self.notebook_info()) | |
@@ -778,7 +840,7 b' class NotebookApp(BaseIPythonApplication):' | |||||
778 |
|
840 | |||
779 | def notebook_info(self): |
|
841 | def notebook_info(self): | |
780 | "Return the current working directory and the server url information" |
|
842 | "Return the current working directory and the server url information" | |
781 |
info = self. |
|
843 | info = self.contents_manager.info_string() + "\n" | |
782 | info += "%d active kernels \n" % len(self.kernel_manager._kernels) |
|
844 | info += "%d active kernels \n" % len(self.kernel_manager._kernels) | |
783 | return info + "The IPython Notebook is running at: %s" % self.display_url |
|
845 | return info + "The IPython Notebook is running at: %s" % self.display_url | |
784 |
|
846 | |||
@@ -790,6 +852,7 b' class NotebookApp(BaseIPythonApplication):' | |||||
790 | 'secure': bool(self.certfile), |
|
852 | 'secure': bool(self.certfile), | |
791 | 'base_url': self.base_url, |
|
853 | 'base_url': self.base_url, | |
792 | 'notebook_dir': os.path.abspath(self.notebook_dir), |
|
854 | 'notebook_dir': os.path.abspath(self.notebook_dir), | |
|
855 | 'pid': os.getpid() | |||
793 | } |
|
856 | } | |
794 |
|
857 | |||
795 | def write_server_info_file(self): |
|
858 | def write_server_info_file(self): | |
@@ -863,8 +926,17 b" def list_running_servers(profile='default'):" | |||||
863 | for file in os.listdir(pd.security_dir): |
|
926 | for file in os.listdir(pd.security_dir): | |
864 | if file.startswith('nbserver-'): |
|
927 | if file.startswith('nbserver-'): | |
865 | with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f: |
|
928 | with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f: | |
866 |
|
|
929 | info = json.load(f) | |
867 |
|
930 | |||
|
931 | # Simple check whether that process is really still running | |||
|
932 | if check_pid(info['pid']): | |||
|
933 | yield info | |||
|
934 | else: | |||
|
935 | # If the process has died, try to delete its info file | |||
|
936 | try: | |||
|
937 | os.unlink(file) | |||
|
938 | except OSError: | |||
|
939 | pass # TODO: This should warn or log or something | |||
868 | #----------------------------------------------------------------------------- |
|
940 | #----------------------------------------------------------------------------- | |
869 | # Main entry point |
|
941 | # Main entry point | |
870 | #----------------------------------------------------------------------------- |
|
942 | #----------------------------------------------------------------------------- |
@@ -21,7 +21,6 b' from zmq.eventloop import ioloop' | |||||
21 |
|
21 | |||
22 | from IPython.config.configurable import LoggingConfigurable |
|
22 | from IPython.config.configurable import LoggingConfigurable | |
23 | from IPython.utils.traitlets import Dict, Instance, CFloat |
|
23 | from IPython.utils.traitlets import Dict, Instance, CFloat | |
24 | from IPython.parallel.apps.ipclusterapp import IPClusterStart |
|
|||
25 | from IPython.core.profileapp import list_profiles_in |
|
24 | from IPython.core.profileapp import list_profiles_in | |
26 | from IPython.core.profiledir import ProfileDir |
|
25 | from IPython.core.profiledir import ProfileDir | |
27 | from IPython.utils import py3compat |
|
26 | from IPython.utils import py3compat | |
@@ -33,17 +32,6 b' from IPython.utils.path import get_ipython_dir' | |||||
33 | #----------------------------------------------------------------------------- |
|
32 | #----------------------------------------------------------------------------- | |
34 |
|
33 | |||
35 |
|
34 | |||
36 | class DummyIPClusterStart(IPClusterStart): |
|
|||
37 | """Dummy subclass to skip init steps that conflict with global app. |
|
|||
38 |
|
||||
39 | Instantiating and initializing this class should result in fully configured |
|
|||
40 | launchers, but no other side effects or state. |
|
|||
41 | """ |
|
|||
42 |
|
||||
43 | def init_signal(self): |
|
|||
44 | pass |
|
|||
45 | def reinit_logging(self): |
|
|||
46 | pass |
|
|||
47 |
|
35 | |||
48 |
|
36 | |||
49 | class ClusterManager(LoggingConfigurable): |
|
37 | class ClusterManager(LoggingConfigurable): | |
@@ -59,6 +47,20 b' class ClusterManager(LoggingConfigurable):' | |||||
59 | return IOLoop.instance() |
|
47 | return IOLoop.instance() | |
60 |
|
48 | |||
61 | def build_launchers(self, profile_dir): |
|
49 | def build_launchers(self, profile_dir): | |
|
50 | from IPython.parallel.apps.ipclusterapp import IPClusterStart | |||
|
51 | ||||
|
52 | class DummyIPClusterStart(IPClusterStart): | |||
|
53 | """Dummy subclass to skip init steps that conflict with global app. | |||
|
54 | ||||
|
55 | Instantiating and initializing this class should result in fully configured | |||
|
56 | launchers, but no other side effects or state. | |||
|
57 | """ | |||
|
58 | ||||
|
59 | def init_signal(self): | |||
|
60 | pass | |||
|
61 | def reinit_logging(self): | |||
|
62 | pass | |||
|
63 | ||||
62 | starter = DummyIPClusterStart(log=self.log) |
|
64 | starter = DummyIPClusterStart(log=self.log) | |
63 | starter.initialize(['--profile-dir', profile_dir]) |
|
65 | starter.initialize(['--profile-dir', profile_dir]) | |
64 | cl = starter.controller_launcher |
|
66 | cl = starter.controller_launcher |
1 | NO CONTENT: file renamed from IPython/html/services/notebooks/__init__.py to IPython/html/services/contents/__init__.py |
|
NO CONTENT: file renamed from IPython/html/services/notebooks/__init__.py to IPython/html/services/contents/__init__.py |
1 | NO CONTENT: file renamed from IPython/html/services/notebooks/tests/__init__.py to IPython/html/services/contents/tests/__init__.py |
|
NO CONTENT: file renamed from IPython/html/services/notebooks/tests/__init__.py to IPython/html/services/contents/tests/__init__.py |
@@ -1,6 +1,7 b'' | |||||
1 | # coding: utf-8 |
|
1 | # coding: utf-8 | |
2 |
"""Test the |
|
2 | """Test the contents webservice API.""" | |
3 |
|
3 | |||
|
4 | import base64 | |||
4 | import io |
|
5 | import io | |
5 | import json |
|
6 | import json | |
6 | import os |
|
7 | import os | |
@@ -21,23 +22,21 b' from IPython.utils import py3compat' | |||||
21 | from IPython.utils.data import uniq_stable |
|
22 | from IPython.utils.data import uniq_stable | |
22 |
|
23 | |||
23 |
|
24 | |||
24 | # TODO: Remove this after we create the contents web service and directories are |
|
25 | def notebooks_only(dir_model): | |
25 | # no longer listed by the notebook web service. |
|
26 | return [nb for nb in dir_model['content'] if nb['type']=='notebook'] | |
26 | def notebooks_only(nb_list): |
|
|||
27 | return [nb for nb in nb_list if nb['type']=='notebook'] |
|
|||
28 |
|
27 | |||
29 |
def dirs_only( |
|
28 | def dirs_only(dir_model): | |
30 |
return [x for x in |
|
29 | return [x for x in dir_model['content'] if x['type']=='directory'] | |
31 |
|
30 | |||
32 |
|
31 | |||
33 |
class |
|
32 | class API(object): | |
34 |
"""Wrapper for |
|
33 | """Wrapper for contents API calls.""" | |
35 | def __init__(self, base_url): |
|
34 | def __init__(self, base_url): | |
36 | self.base_url = base_url |
|
35 | self.base_url = base_url | |
37 |
|
36 | |||
38 | def _req(self, verb, path, body=None): |
|
37 | def _req(self, verb, path, body=None): | |
39 | response = requests.request(verb, |
|
38 | response = requests.request(verb, | |
40 |
url_path_join(self.base_url, 'api/ |
|
39 | url_path_join(self.base_url, 'api/contents', path), | |
41 | data=body, |
|
40 | data=body, | |
42 | ) |
|
41 | ) | |
43 | response.raise_for_status() |
|
42 | response.raise_for_status() | |
@@ -49,8 +48,11 b' class NBAPI(object):' | |||||
49 | def read(self, name, path='/'): |
|
48 | def read(self, name, path='/'): | |
50 | return self._req('GET', url_path_join(path, name)) |
|
49 | return self._req('GET', url_path_join(path, name)) | |
51 |
|
50 | |||
52 | def create_untitled(self, path='/'): |
|
51 | def create_untitled(self, path='/', ext=None): | |
53 | return self._req('POST', path) |
|
52 | body = None | |
|
53 | if ext: | |||
|
54 | body = json.dumps({'ext': ext}) | |||
|
55 | return self._req('POST', path, body) | |||
54 |
|
56 | |||
55 | def upload_untitled(self, body, path='/'): |
|
57 | def upload_untitled(self, body, path='/'): | |
56 | return self._req('POST', path, body) |
|
58 | return self._req('POST', path, body) | |
@@ -65,6 +67,9 b' class NBAPI(object):' | |||||
65 | def upload(self, name, body, path='/'): |
|
67 | def upload(self, name, body, path='/'): | |
66 | return self._req('PUT', url_path_join(path, name), body) |
|
68 | return self._req('PUT', url_path_join(path, name), body) | |
67 |
|
69 | |||
|
70 | def mkdir(self, name, path='/'): | |||
|
71 | return self._req('PUT', url_path_join(path, name), json.dumps({'type': 'directory'})) | |||
|
72 | ||||
68 | def copy(self, copy_from, copy_to, path='/'): |
|
73 | def copy(self, copy_from, copy_to, path='/'): | |
69 | body = json.dumps({'copy_from':copy_from}) |
|
74 | body = json.dumps({'copy_from':copy_from}) | |
70 | return self._req('PUT', url_path_join(path, copy_to), body) |
|
75 | return self._req('PUT', url_path_join(path, copy_to), body) | |
@@ -112,8 +117,20 b' class APITest(NotebookTestBase):' | |||||
112 | del dirs[0] # remove '' |
|
117 | del dirs[0] # remove '' | |
113 | top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs} |
|
118 | top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs} | |
114 |
|
119 | |||
|
120 | @staticmethod | |||
|
121 | def _blob_for_name(name): | |||
|
122 | return name.encode('utf-8') + b'\xFF' | |||
|
123 | ||||
|
124 | @staticmethod | |||
|
125 | def _txt_for_name(name): | |||
|
126 | return u'%s text file' % name | |||
|
127 | ||||
115 | def setUp(self): |
|
128 | def setUp(self): | |
116 | nbdir = self.notebook_dir.name |
|
129 | nbdir = self.notebook_dir.name | |
|
130 | self.blob = os.urandom(100) | |||
|
131 | self.b64_blob = base64.encodestring(self.blob).decode('ascii') | |||
|
132 | ||||
|
133 | ||||
117 |
|
134 | |||
118 | for d in (self.dirs + self.hidden_dirs): |
|
135 | for d in (self.dirs + self.hidden_dirs): | |
119 | d.replace('/', os.sep) |
|
136 | d.replace('/', os.sep) | |
@@ -122,12 +139,22 b' class APITest(NotebookTestBase):' | |||||
122 |
|
139 | |||
123 | for d, name in self.dirs_nbs: |
|
140 | for d, name in self.dirs_nbs: | |
124 | d = d.replace('/', os.sep) |
|
141 | d = d.replace('/', os.sep) | |
|
142 | # create a notebook | |||
125 | with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w', |
|
143 | with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w', | |
126 | encoding='utf-8') as f: |
|
144 | encoding='utf-8') as f: | |
127 | nb = new_notebook(name=name) |
|
145 | nb = new_notebook(name=name) | |
128 | write(nb, f, format='ipynb') |
|
146 | write(nb, f, format='ipynb') | |
129 |
|
147 | |||
130 | self.nb_api = NBAPI(self.base_url()) |
|
148 | # create a text file | |
|
149 | with io.open(pjoin(nbdir, d, '%s.txt' % name), 'w', | |||
|
150 | encoding='utf-8') as f: | |||
|
151 | f.write(self._txt_for_name(name)) | |||
|
152 | ||||
|
153 | # create a binary file | |||
|
154 | with io.open(pjoin(nbdir, d, '%s.blob' % name), 'wb') as f: | |||
|
155 | f.write(self._blob_for_name(name)) | |||
|
156 | ||||
|
157 | self.api = API(self.base_url()) | |||
131 |
|
158 | |||
132 | def tearDown(self): |
|
159 | def tearDown(self): | |
133 | nbdir = self.notebook_dir.name |
|
160 | nbdir = self.notebook_dir.name | |
@@ -139,175 +166,287 b' class APITest(NotebookTestBase):' | |||||
139 | os.unlink(pjoin(nbdir, 'inroot.ipynb')) |
|
166 | os.unlink(pjoin(nbdir, 'inroot.ipynb')) | |
140 |
|
167 | |||
141 | def test_list_notebooks(self): |
|
168 | def test_list_notebooks(self): | |
142 |
nbs = notebooks_only(self. |
|
169 | nbs = notebooks_only(self.api.list().json()) | |
143 | self.assertEqual(len(nbs), 1) |
|
170 | self.assertEqual(len(nbs), 1) | |
144 | self.assertEqual(nbs[0]['name'], 'inroot.ipynb') |
|
171 | self.assertEqual(nbs[0]['name'], 'inroot.ipynb') | |
145 |
|
172 | |||
146 |
nbs = notebooks_only(self. |
|
173 | nbs = notebooks_only(self.api.list('/Directory with spaces in/').json()) | |
147 | self.assertEqual(len(nbs), 1) |
|
174 | self.assertEqual(len(nbs), 1) | |
148 | self.assertEqual(nbs[0]['name'], 'inspace.ipynb') |
|
175 | self.assertEqual(nbs[0]['name'], 'inspace.ipynb') | |
149 |
|
176 | |||
150 |
nbs = notebooks_only(self. |
|
177 | nbs = notebooks_only(self.api.list(u'/unicodé/').json()) | |
151 | self.assertEqual(len(nbs), 1) |
|
178 | self.assertEqual(len(nbs), 1) | |
152 | self.assertEqual(nbs[0]['name'], 'innonascii.ipynb') |
|
179 | self.assertEqual(nbs[0]['name'], 'innonascii.ipynb') | |
153 | self.assertEqual(nbs[0]['path'], u'unicodé') |
|
180 | self.assertEqual(nbs[0]['path'], u'unicodé') | |
154 |
|
181 | |||
155 |
nbs = notebooks_only(self. |
|
182 | nbs = notebooks_only(self.api.list('/foo/bar/').json()) | |
156 | self.assertEqual(len(nbs), 1) |
|
183 | self.assertEqual(len(nbs), 1) | |
157 | self.assertEqual(nbs[0]['name'], 'baz.ipynb') |
|
184 | self.assertEqual(nbs[0]['name'], 'baz.ipynb') | |
158 | self.assertEqual(nbs[0]['path'], 'foo/bar') |
|
185 | self.assertEqual(nbs[0]['path'], 'foo/bar') | |
159 |
|
186 | |||
160 |
nbs = notebooks_only(self. |
|
187 | nbs = notebooks_only(self.api.list('foo').json()) | |
161 | self.assertEqual(len(nbs), 4) |
|
188 | self.assertEqual(len(nbs), 4) | |
162 | nbnames = { normalize('NFC', n['name']) for n in nbs } |
|
189 | nbnames = { normalize('NFC', n['name']) for n in nbs } | |
163 | expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb'] |
|
190 | expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb'] | |
164 | expected = { normalize('NFC', name) for name in expected } |
|
191 | expected = { normalize('NFC', name) for name in expected } | |
165 | self.assertEqual(nbnames, expected) |
|
192 | self.assertEqual(nbnames, expected) | |
166 |
|
193 | |||
167 |
nbs = notebooks_only(self. |
|
194 | nbs = notebooks_only(self.api.list('ordering').json()) | |
168 | nbnames = [n['name'] for n in nbs] |
|
195 | nbnames = [n['name'] for n in nbs] | |
169 | expected = ['A.ipynb', 'b.ipynb', 'C.ipynb'] |
|
196 | expected = ['A.ipynb', 'b.ipynb', 'C.ipynb'] | |
170 | self.assertEqual(nbnames, expected) |
|
197 | self.assertEqual(nbnames, expected) | |
171 |
|
198 | |||
172 | def test_list_dirs(self): |
|
199 | def test_list_dirs(self): | |
173 |
dirs = dirs_only(self. |
|
200 | dirs = dirs_only(self.api.list().json()) | |
174 | dir_names = {normalize('NFC', d['name']) for d in dirs} |
|
201 | dir_names = {normalize('NFC', d['name']) for d in dirs} | |
175 | self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs |
|
202 | self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs | |
176 |
|
203 | |||
177 | def test_list_nonexistant_dir(self): |
|
204 | def test_list_nonexistant_dir(self): | |
178 | with assert_http_error(404): |
|
205 | with assert_http_error(404): | |
179 |
self. |
|
206 | self.api.list('nonexistant') | |
180 |
|
207 | |||
181 | def test_get_contents(self): |
|
208 | def test_get_nb_contents(self): | |
182 | for d, name in self.dirs_nbs: |
|
209 | for d, name in self.dirs_nbs: | |
183 |
nb = self. |
|
210 | nb = self.api.read('%s.ipynb' % name, d+'/').json() | |
184 | self.assertEqual(nb['name'], u'%s.ipynb' % name) |
|
211 | self.assertEqual(nb['name'], u'%s.ipynb' % name) | |
|
212 | self.assertEqual(nb['type'], 'notebook') | |||
|
213 | self.assertIn('content', nb) | |||
|
214 | self.assertEqual(nb['format'], 'json') | |||
185 | self.assertIn('content', nb) |
|
215 | self.assertIn('content', nb) | |
186 | self.assertIn('metadata', nb['content']) |
|
216 | self.assertIn('metadata', nb['content']) | |
187 | self.assertIsInstance(nb['content']['metadata'], dict) |
|
217 | self.assertIsInstance(nb['content']['metadata'], dict) | |
188 |
|
218 | |||
|
219 | def test_get_contents_no_such_file(self): | |||
|
220 | # Name that doesn't exist - should be a 404 | |||
|
221 | with assert_http_error(404): | |||
|
222 | self.api.read('q.ipynb', 'foo') | |||
|
223 | ||||
|
224 | def test_get_text_file_contents(self): | |||
|
225 | for d, name in self.dirs_nbs: | |||
|
226 | model = self.api.read(u'%s.txt' % name, d+'/').json() | |||
|
227 | self.assertEqual(model['name'], u'%s.txt' % name) | |||
|
228 | self.assertIn('content', model) | |||
|
229 | self.assertEqual(model['format'], 'text') | |||
|
230 | self.assertEqual(model['type'], 'file') | |||
|
231 | self.assertEqual(model['content'], self._txt_for_name(name)) | |||
|
232 | ||||
|
233 | # Name that doesn't exist - should be a 404 | |||
|
234 | with assert_http_error(404): | |||
|
235 | self.api.read('q.txt', 'foo') | |||
|
236 | ||||
|
237 | def test_get_binary_file_contents(self): | |||
|
238 | for d, name in self.dirs_nbs: | |||
|
239 | model = self.api.read(u'%s.blob' % name, d+'/').json() | |||
|
240 | self.assertEqual(model['name'], u'%s.blob' % name) | |||
|
241 | self.assertIn('content', model) | |||
|
242 | self.assertEqual(model['format'], 'base64') | |||
|
243 | self.assertEqual(model['type'], 'file') | |||
|
244 | b64_data = base64.encodestring(self._blob_for_name(name)).decode('ascii') | |||
|
245 | self.assertEqual(model['content'], b64_data) | |||
|
246 | ||||
189 | # Name that doesn't exist - should be a 404 |
|
247 | # Name that doesn't exist - should be a 404 | |
190 | with assert_http_error(404): |
|
248 | with assert_http_error(404): | |
191 |
self. |
|
249 | self.api.read('q.txt', 'foo') | |
192 |
|
250 | |||
193 |
def _check_ |
|
251 | def _check_created(self, resp, name, path, type='notebook'): | |
194 | self.assertEqual(resp.status_code, 201) |
|
252 | self.assertEqual(resp.status_code, 201) | |
195 | location_header = py3compat.str_to_unicode(resp.headers['Location']) |
|
253 | location_header = py3compat.str_to_unicode(resp.headers['Location']) | |
196 |
self.assertEqual(location_header, url_escape(url_path_join(u'/api/ |
|
254 | self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path, name))) | |
197 | self.assertEqual(resp.json()['name'], name) |
|
255 | rjson = resp.json() | |
198 | assert os.path.isfile(pjoin( |
|
256 | self.assertEqual(rjson['name'], name) | |
|
257 | self.assertEqual(rjson['path'], path) | |||
|
258 | self.assertEqual(rjson['type'], type) | |||
|
259 | isright = os.path.isdir if type == 'directory' else os.path.isfile | |||
|
260 | assert isright(pjoin( | |||
199 | self.notebook_dir.name, |
|
261 | self.notebook_dir.name, | |
200 | path.replace('/', os.sep), |
|
262 | path.replace('/', os.sep), | |
201 | name, |
|
263 | name, | |
202 | )) |
|
264 | )) | |
203 |
|
265 | |||
204 | def test_create_untitled(self): |
|
266 | def test_create_untitled(self): | |
205 |
resp = self. |
|
267 | resp = self.api.create_untitled(path=u'å b') | |
206 |
self._check_ |
|
268 | self._check_created(resp, 'Untitled0.ipynb', u'å b') | |
207 |
|
269 | |||
208 | # Second time |
|
270 | # Second time | |
209 |
resp = self. |
|
271 | resp = self.api.create_untitled(path=u'å b') | |
210 |
self._check_ |
|
272 | self._check_created(resp, 'Untitled1.ipynb', u'å b') | |
211 |
|
273 | |||
212 | # And two directories down |
|
274 | # And two directories down | |
213 |
resp = self. |
|
275 | resp = self.api.create_untitled(path='foo/bar') | |
214 |
self._check_ |
|
276 | self._check_created(resp, 'Untitled0.ipynb', 'foo/bar') | |
|
277 | ||||
|
278 | def test_create_untitled_txt(self): | |||
|
279 | resp = self.api.create_untitled(path='foo/bar', ext='.txt') | |||
|
280 | self._check_created(resp, 'untitled0.txt', 'foo/bar', type='file') | |||
|
281 | ||||
|
282 | resp = self.api.read(path='foo/bar', name='untitled0.txt') | |||
|
283 | model = resp.json() | |||
|
284 | self.assertEqual(model['type'], 'file') | |||
|
285 | self.assertEqual(model['format'], 'text') | |||
|
286 | self.assertEqual(model['content'], '') | |||
215 |
|
287 | |||
216 | def test_upload_untitled(self): |
|
288 | def test_upload_untitled(self): | |
217 | nb = new_notebook(name='Upload test') |
|
289 | nb = new_notebook(name='Upload test') | |
218 | nbmodel = {'content': nb} |
|
290 | nbmodel = {'content': nb, 'type': 'notebook'} | |
219 |
resp = self. |
|
291 | resp = self.api.upload_untitled(path=u'å b', | |
220 | body=json.dumps(nbmodel)) |
|
292 | body=json.dumps(nbmodel)) | |
221 |
self._check_ |
|
293 | self._check_created(resp, 'Untitled0.ipynb', u'å b') | |
222 |
|
294 | |||
223 | def test_upload(self): |
|
295 | def test_upload(self): | |
224 | nb = new_notebook(name=u'ignored') |
|
296 | nb = new_notebook(name=u'ignored') | |
225 | nbmodel = {'content': nb} |
|
297 | nbmodel = {'content': nb, 'type': 'notebook'} | |
226 |
resp = self. |
|
298 | resp = self.api.upload(u'Upload tést.ipynb', path=u'å b', | |
227 | body=json.dumps(nbmodel)) |
|
299 | body=json.dumps(nbmodel)) | |
228 |
self._check_ |
|
300 | self._check_created(resp, u'Upload tést.ipynb', u'å b') | |
|
301 | ||||
|
302 | def test_mkdir(self): | |||
|
303 | resp = self.api.mkdir(u'New ∂ir', path=u'å b') | |||
|
304 | self._check_created(resp, u'New ∂ir', u'å b', type='directory') | |||
|
305 | ||||
|
306 | def test_mkdir_hidden_400(self): | |||
|
307 | with assert_http_error(400): | |||
|
308 | resp = self.api.mkdir(u'.hidden', path=u'å b') | |||
|
309 | ||||
|
310 | def test_upload_txt(self): | |||
|
311 | body = u'ünicode téxt' | |||
|
312 | model = { | |||
|
313 | 'content' : body, | |||
|
314 | 'format' : 'text', | |||
|
315 | 'type' : 'file', | |||
|
316 | } | |||
|
317 | resp = self.api.upload(u'Upload tést.txt', path=u'å b', | |||
|
318 | body=json.dumps(model)) | |||
|
319 | ||||
|
320 | # check roundtrip | |||
|
321 | resp = self.api.read(path=u'å b', name=u'Upload tést.txt') | |||
|
322 | model = resp.json() | |||
|
323 | self.assertEqual(model['type'], 'file') | |||
|
324 | self.assertEqual(model['format'], 'text') | |||
|
325 | self.assertEqual(model['content'], body) | |||
|
326 | ||||
|
327 | def test_upload_b64(self): | |||
|
328 | body = b'\xFFblob' | |||
|
329 | b64body = base64.encodestring(body).decode('ascii') | |||
|
330 | model = { | |||
|
331 | 'content' : b64body, | |||
|
332 | 'format' : 'base64', | |||
|
333 | 'type' : 'file', | |||
|
334 | } | |||
|
335 | resp = self.api.upload(u'Upload tést.blob', path=u'å b', | |||
|
336 | body=json.dumps(model)) | |||
|
337 | ||||
|
338 | # check roundtrip | |||
|
339 | resp = self.api.read(path=u'å b', name=u'Upload tést.blob') | |||
|
340 | model = resp.json() | |||
|
341 | self.assertEqual(model['type'], 'file') | |||
|
342 | self.assertEqual(model['format'], 'base64') | |||
|
343 | decoded = base64.decodestring(model['content'].encode('ascii')) | |||
|
344 | self.assertEqual(decoded, body) | |||
229 |
|
345 | |||
230 | def test_upload_v2(self): |
|
346 | def test_upload_v2(self): | |
231 | nb = v2.new_notebook() |
|
347 | nb = v2.new_notebook() | |
232 | ws = v2.new_worksheet() |
|
348 | ws = v2.new_worksheet() | |
233 | nb.worksheets.append(ws) |
|
349 | nb.worksheets.append(ws) | |
234 | ws.cells.append(v2.new_code_cell(input='print("hi")')) |
|
350 | ws.cells.append(v2.new_code_cell(input='print("hi")')) | |
235 | nbmodel = {'content': nb} |
|
351 | nbmodel = {'content': nb, 'type': 'notebook'} | |
236 |
resp = self. |
|
352 | resp = self.api.upload(u'Upload tést.ipynb', path=u'å b', | |
237 | body=json.dumps(nbmodel)) |
|
353 | body=json.dumps(nbmodel)) | |
238 |
self._check_ |
|
354 | self._check_created(resp, u'Upload tést.ipynb', u'å b') | |
239 |
resp = self. |
|
355 | resp = self.api.read(u'Upload tést.ipynb', u'å b') | |
240 | data = resp.json() |
|
356 | data = resp.json() | |
241 | self.assertEqual(data['content']['nbformat'], current.nbformat) |
|
357 | self.assertEqual(data['content']['nbformat'], current.nbformat) | |
242 | self.assertEqual(data['content']['orig_nbformat'], 2) |
|
358 | self.assertEqual(data['content']['orig_nbformat'], 2) | |
243 |
|
359 | |||
244 | def test_copy_untitled(self): |
|
360 | def test_copy_untitled(self): | |
245 |
resp = self. |
|
361 | resp = self.api.copy_untitled(u'ç d.ipynb', path=u'å b') | |
246 |
self._check_ |
|
362 | self._check_created(resp, u'ç d-Copy0.ipynb', u'å b') | |
247 |
|
363 | |||
248 | def test_copy(self): |
|
364 | def test_copy(self): | |
249 |
resp = self. |
|
365 | resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b') | |
250 |
self._check_ |
|
366 | self._check_created(resp, u'cøpy.ipynb', u'å b') | |
|
367 | ||||
|
368 | def test_copy_path(self): | |||
|
369 | resp = self.api.copy(u'foo/a.ipynb', u'cøpyfoo.ipynb', path=u'å b') | |||
|
370 | self._check_created(resp, u'cøpyfoo.ipynb', u'å b') | |||
|
371 | ||||
|
372 | def test_copy_dir_400(self): | |||
|
373 | # can't copy directories | |||
|
374 | with assert_http_error(400): | |||
|
375 | resp = self.api.copy(u'å b', u'å c') | |||
251 |
|
376 | |||
252 | def test_delete(self): |
|
377 | def test_delete(self): | |
253 | for d, name in self.dirs_nbs: |
|
378 | for d, name in self.dirs_nbs: | |
254 |
resp = self. |
|
379 | resp = self.api.delete('%s.ipynb' % name, d) | |
255 | self.assertEqual(resp.status_code, 204) |
|
380 | self.assertEqual(resp.status_code, 204) | |
256 |
|
381 | |||
257 | for d in self.dirs + ['/']: |
|
382 | for d in self.dirs + ['/']: | |
258 |
nbs = notebooks_only(self. |
|
383 | nbs = notebooks_only(self.api.list(d).json()) | |
259 | self.assertEqual(len(nbs), 0) |
|
384 | self.assertEqual(len(nbs), 0) | |
260 |
|
385 | |||
|
386 | def test_delete_dirs(self): | |||
|
387 | # depth-first delete everything, so we don't try to delete empty directories | |||
|
388 | for name in sorted(self.dirs + ['/'], key=len, reverse=True): | |||
|
389 | listing = self.api.list(name).json()['content'] | |||
|
390 | for model in listing: | |||
|
391 | self.api.delete(model['name'], model['path']) | |||
|
392 | listing = self.api.list('/').json()['content'] | |||
|
393 | self.assertEqual(listing, []) | |||
|
394 | ||||
|
395 | def test_delete_non_empty_dir(self): | |||
|
396 | """delete non-empty dir raises 400""" | |||
|
397 | with assert_http_error(400): | |||
|
398 | self.api.delete(u'å b') | |||
|
399 | ||||
261 | def test_rename(self): |
|
400 | def test_rename(self): | |
262 |
resp = self. |
|
401 | resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb') | |
263 | self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb') |
|
402 | self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb') | |
264 | self.assertEqual(resp.json()['name'], 'z.ipynb') |
|
403 | self.assertEqual(resp.json()['name'], 'z.ipynb') | |
265 | assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb')) |
|
404 | assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb')) | |
266 |
|
405 | |||
267 |
nbs = notebooks_only(self. |
|
406 | nbs = notebooks_only(self.api.list('foo').json()) | |
268 | nbnames = set(n['name'] for n in nbs) |
|
407 | nbnames = set(n['name'] for n in nbs) | |
269 | self.assertIn('z.ipynb', nbnames) |
|
408 | self.assertIn('z.ipynb', nbnames) | |
270 | self.assertNotIn('a.ipynb', nbnames) |
|
409 | self.assertNotIn('a.ipynb', nbnames) | |
271 |
|
410 | |||
272 | def test_rename_existing(self): |
|
411 | def test_rename_existing(self): | |
273 | with assert_http_error(409): |
|
412 | with assert_http_error(409): | |
274 |
self. |
|
413 | self.api.rename('a.ipynb', 'foo', 'b.ipynb') | |
275 |
|
414 | |||
276 | def test_save(self): |
|
415 | def test_save(self): | |
277 |
resp = self. |
|
416 | resp = self.api.read('a.ipynb', 'foo') | |
278 | nbcontent = json.loads(resp.text)['content'] |
|
417 | nbcontent = json.loads(resp.text)['content'] | |
279 | nb = to_notebook_json(nbcontent) |
|
418 | nb = to_notebook_json(nbcontent) | |
280 | ws = new_worksheet() |
|
419 | ws = new_worksheet() | |
281 | nb.worksheets = [ws] |
|
420 | nb.worksheets = [ws] | |
282 | ws.cells.append(new_heading_cell(u'Created by test ³')) |
|
421 | ws.cells.append(new_heading_cell(u'Created by test ³')) | |
283 |
|
422 | |||
284 | nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb} |
|
423 | nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'} | |
285 |
resp = self. |
|
424 | resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel)) | |
286 |
|
425 | |||
287 | nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb') |
|
426 | nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb') | |
288 | with io.open(nbfile, 'r', encoding='utf-8') as f: |
|
427 | with io.open(nbfile, 'r', encoding='utf-8') as f: | |
289 | newnb = read(f, format='ipynb') |
|
428 | newnb = read(f, format='ipynb') | |
290 | self.assertEqual(newnb.worksheets[0].cells[0].source, |
|
429 | self.assertEqual(newnb.worksheets[0].cells[0].source, | |
291 | u'Created by test ³') |
|
430 | u'Created by test ³') | |
292 |
nbcontent = self. |
|
431 | nbcontent = self.api.read('a.ipynb', 'foo').json()['content'] | |
293 | newnb = to_notebook_json(nbcontent) |
|
432 | newnb = to_notebook_json(nbcontent) | |
294 | self.assertEqual(newnb.worksheets[0].cells[0].source, |
|
433 | self.assertEqual(newnb.worksheets[0].cells[0].source, | |
295 | u'Created by test ³') |
|
434 | u'Created by test ³') | |
296 |
|
435 | |||
297 | # Save and rename |
|
436 | # Save and rename | |
298 | nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb} |
|
437 | nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb, 'type': 'notebook'} | |
299 |
resp = self. |
|
438 | resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel)) | |
300 | saved = resp.json() |
|
439 | saved = resp.json() | |
301 | self.assertEqual(saved['name'], 'a2.ipynb') |
|
440 | self.assertEqual(saved['name'], 'a2.ipynb') | |
302 | self.assertEqual(saved['path'], 'foo/bar') |
|
441 | self.assertEqual(saved['path'], 'foo/bar') | |
303 | assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb')) |
|
442 | assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb')) | |
304 | assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')) |
|
443 | assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')) | |
305 | with assert_http_error(404): |
|
444 | with assert_http_error(404): | |
306 |
self. |
|
445 | self.api.read('a.ipynb', 'foo') | |
307 |
|
446 | |||
308 | def test_checkpoints(self): |
|
447 | def test_checkpoints(self): | |
309 |
resp = self. |
|
448 | resp = self.api.read('a.ipynb', 'foo') | |
310 |
r = self. |
|
449 | r = self.api.new_checkpoint('a.ipynb', 'foo') | |
311 | self.assertEqual(r.status_code, 201) |
|
450 | self.assertEqual(r.status_code, 201) | |
312 | cp1 = r.json() |
|
451 | cp1 = r.json() | |
313 | self.assertEqual(set(cp1), {'id', 'last_modified'}) |
|
452 | self.assertEqual(set(cp1), {'id', 'last_modified'}) | |
@@ -321,27 +460,26 b' class APITest(NotebookTestBase):' | |||||
321 | hcell = new_heading_cell('Created by test') |
|
460 | hcell = new_heading_cell('Created by test') | |
322 | ws.cells.append(hcell) |
|
461 | ws.cells.append(hcell) | |
323 | # Save |
|
462 | # Save | |
324 | nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb} |
|
463 | nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'} | |
325 |
resp = self. |
|
464 | resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel)) | |
326 |
|
465 | |||
327 | # List checkpoints |
|
466 | # List checkpoints | |
328 |
cps = self. |
|
467 | cps = self.api.get_checkpoints('a.ipynb', 'foo').json() | |
329 | self.assertEqual(cps, [cp1]) |
|
468 | self.assertEqual(cps, [cp1]) | |
330 |
|
469 | |||
331 |
nbcontent = self. |
|
470 | nbcontent = self.api.read('a.ipynb', 'foo').json()['content'] | |
332 | nb = to_notebook_json(nbcontent) |
|
471 | nb = to_notebook_json(nbcontent) | |
333 | self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test') |
|
472 | self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test') | |
334 |
|
473 | |||
335 | # Restore cp1 |
|
474 | # Restore cp1 | |
336 |
r = self. |
|
475 | r = self.api.restore_checkpoint('a.ipynb', 'foo', cp1['id']) | |
337 | self.assertEqual(r.status_code, 204) |
|
476 | self.assertEqual(r.status_code, 204) | |
338 |
nbcontent = self. |
|
477 | nbcontent = self.api.read('a.ipynb', 'foo').json()['content'] | |
339 | nb = to_notebook_json(nbcontent) |
|
478 | nb = to_notebook_json(nbcontent) | |
340 | self.assertEqual(nb.worksheets, []) |
|
479 | self.assertEqual(nb.worksheets, []) | |
341 |
|
480 | |||
342 | # Delete cp1 |
|
481 | # Delete cp1 | |
343 |
r = self. |
|
482 | r = self.api.delete_checkpoint('a.ipynb', 'foo', cp1['id']) | |
344 | self.assertEqual(r.status_code, 204) |
|
483 | self.assertEqual(r.status_code, 204) | |
345 |
cps = self. |
|
484 | cps = self.api.get_checkpoints('a.ipynb', 'foo').json() | |
346 | self.assertEqual(cps, []) |
|
485 | self.assertEqual(cps, []) | |
347 |
|
@@ -15,74 +15,74 b' from IPython.utils.tempdir import TemporaryDirectory' | |||||
15 | from IPython.utils.traitlets import TraitError |
|
15 | from IPython.utils.traitlets import TraitError | |
16 | from IPython.html.utils import url_path_join |
|
16 | from IPython.html.utils import url_path_join | |
17 |
|
17 | |||
18 |
from ..file |
|
18 | from ..filemanager import FileContentsManager | |
19 |
from .. |
|
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 | with TemporaryDirectory() as td: |
|
25 | with TemporaryDirectory() as td: | |
26 |
fm = File |
|
26 | fm = FileContentsManager(root_dir=td) | |
27 |
self.assertEqual(fm. |
|
27 | self.assertEqual(fm.root_dir, td) | |
28 |
|
28 | |||
29 |
def test_missing_ |
|
29 | def test_missing_root_dir(self): | |
30 | with TemporaryDirectory() as td: |
|
30 | with TemporaryDirectory() as td: | |
31 |
|
|
31 | root = os.path.join(td, 'notebook', 'dir', 'is', 'missing') | |
32 |
self.assertRaises(TraitError, File |
|
32 | self.assertRaises(TraitError, FileContentsManager, root_dir=root) | |
33 |
|
33 | |||
34 |
def test_invalid_ |
|
34 | def test_invalid_root_dir(self): | |
35 | with NamedTemporaryFile() as tf: |
|
35 | with NamedTemporaryFile() as tf: | |
36 |
self.assertRaises(TraitError, File |
|
36 | self.assertRaises(TraitError, FileContentsManager, root_dir=tf.name) | |
37 |
|
37 | |||
38 | def test_get_os_path(self): |
|
38 | def test_get_os_path(self): | |
39 | # full filesystem path should be returned with correct operating system |
|
39 | # full filesystem path should be returned with correct operating system | |
40 | # separators. |
|
40 | # separators. | |
41 | with TemporaryDirectory() as td: |
|
41 | with TemporaryDirectory() as td: | |
42 |
|
|
42 | root = td | |
43 |
fm = File |
|
43 | fm = FileContentsManager(root_dir=root) | |
44 | path = fm._get_os_path('test.ipynb', '/path/to/notebook/') |
|
44 | path = fm._get_os_path('test.ipynb', '/path/to/notebook/') | |
45 | rel_path_list = '/path/to/notebook/test.ipynb'.split('/') |
|
45 | rel_path_list = '/path/to/notebook/test.ipynb'.split('/') | |
46 |
fs_path = os.path.join(fm. |
|
46 | fs_path = os.path.join(fm.root_dir, *rel_path_list) | |
47 | self.assertEqual(path, fs_path) |
|
47 | self.assertEqual(path, fs_path) | |
48 |
|
48 | |||
49 |
fm = File |
|
49 | fm = FileContentsManager(root_dir=root) | |
50 | path = fm._get_os_path('test.ipynb') |
|
50 | path = fm._get_os_path('test.ipynb') | |
51 |
fs_path = os.path.join(fm. |
|
51 | fs_path = os.path.join(fm.root_dir, 'test.ipynb') | |
52 | self.assertEqual(path, fs_path) |
|
52 | self.assertEqual(path, fs_path) | |
53 |
|
53 | |||
54 |
fm = File |
|
54 | fm = FileContentsManager(root_dir=root) | |
55 | path = fm._get_os_path('test.ipynb', '////') |
|
55 | path = fm._get_os_path('test.ipynb', '////') | |
56 |
fs_path = os.path.join(fm. |
|
56 | fs_path = os.path.join(fm.root_dir, 'test.ipynb') | |
57 | self.assertEqual(path, fs_path) |
|
57 | self.assertEqual(path, fs_path) | |
58 |
|
58 | |||
59 | def test_checkpoint_subdir(self): |
|
59 | def test_checkpoint_subdir(self): | |
60 | subd = u'sub ∂ir' |
|
60 | subd = u'sub ∂ir' | |
61 | cp_name = 'test-cp.ipynb' |
|
61 | cp_name = 'test-cp.ipynb' | |
62 | with TemporaryDirectory() as td: |
|
62 | with TemporaryDirectory() as td: | |
63 |
|
|
63 | root = td | |
64 | os.mkdir(os.path.join(td, subd)) |
|
64 | os.mkdir(os.path.join(td, subd)) | |
65 |
fm = File |
|
65 | fm = FileContentsManager(root_dir=root) | |
66 | cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/') |
|
66 | cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/') | |
67 | cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd) |
|
67 | cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd) | |
68 | self.assertNotEqual(cp_dir, cp_subdir) |
|
68 | self.assertNotEqual(cp_dir, cp_subdir) | |
69 |
self.assertEqual(cp_dir, os.path.join( |
|
69 | self.assertEqual(cp_dir, os.path.join(root, fm.checkpoint_dir, cp_name)) | |
70 |
self.assertEqual(cp_subdir, os.path.join( |
|
70 | self.assertEqual(cp_subdir, os.path.join(root, subd, fm.checkpoint_dir, cp_name)) | |
71 |
|
71 | |||
|
72 | ||||
|
73 | class TestContentsManager(TestCase): | |||
72 |
|
74 | |||
73 | class TestNotebookManager(TestCase): |
|
|||
74 |
|
||||
75 | def setUp(self): |
|
75 | def setUp(self): | |
76 | self._temp_dir = TemporaryDirectory() |
|
76 | self._temp_dir = TemporaryDirectory() | |
77 | self.td = self._temp_dir.name |
|
77 | self.td = self._temp_dir.name | |
78 |
self. |
|
78 | self.contents_manager = FileContentsManager( | |
79 |
|
|
79 | root_dir=self.td, | |
80 | log=logging.getLogger() |
|
80 | log=logging.getLogger() | |
81 | ) |
|
81 | ) | |
82 |
|
82 | |||
83 | def tearDown(self): |
|
83 | def tearDown(self): | |
84 | self._temp_dir.cleanup() |
|
84 | self._temp_dir.cleanup() | |
85 |
|
85 | |||
86 | def make_dir(self, abs_path, rel_path): |
|
86 | def make_dir(self, abs_path, rel_path): | |
87 | """make subdirectory, rel_path is the relative path |
|
87 | """make subdirectory, rel_path is the relative path | |
88 | to that directory from the location where the server started""" |
|
88 | to that directory from the location where the server started""" | |
@@ -91,31 +91,31 b' class TestNotebookManager(TestCase):' | |||||
91 | os.makedirs(os_path) |
|
91 | os.makedirs(os_path) | |
92 | except OSError: |
|
92 | except OSError: | |
93 | print("Directory already exists: %r" % os_path) |
|
93 | print("Directory already exists: %r" % os_path) | |
94 |
|
94 | |||
95 | def add_code_cell(self, nb): |
|
95 | def add_code_cell(self, nb): | |
96 | output = current.new_output("display_data", output_javascript="alert('hi');") |
|
96 | output = current.new_output("display_data", output_javascript="alert('hi');") | |
97 | cell = current.new_code_cell("print('hi')", outputs=[output]) |
|
97 | cell = current.new_code_cell("print('hi')", outputs=[output]) | |
98 | if not nb.worksheets: |
|
98 | if not nb.worksheets: | |
99 | nb.worksheets.append(current.new_worksheet()) |
|
99 | nb.worksheets.append(current.new_worksheet()) | |
100 | nb.worksheets[0].cells.append(cell) |
|
100 | nb.worksheets[0].cells.append(cell) | |
101 |
|
101 | |||
102 | def new_notebook(self): |
|
102 | def new_notebook(self): | |
103 |
|
|
103 | cm = self.contents_manager | |
104 |
model = |
|
104 | model = cm.create_file() | |
105 | name = model['name'] |
|
105 | name = model['name'] | |
106 | path = model['path'] |
|
106 | path = model['path'] | |
107 |
|
107 | |||
108 |
full_model = |
|
108 | full_model = cm.get_model(name, path) | |
109 | nb = full_model['content'] |
|
109 | nb = full_model['content'] | |
110 | self.add_code_cell(nb) |
|
110 | self.add_code_cell(nb) | |
111 |
|
111 | |||
112 |
|
|
112 | cm.save(full_model, name, path) | |
113 | return nb, name, path |
|
113 | return nb, name, path | |
114 |
|
114 | |||
115 |
def test_create_ |
|
115 | def test_create_file(self): | |
116 |
|
|
116 | cm = self.contents_manager | |
117 | # Test in root directory |
|
117 | # Test in root directory | |
118 |
model = |
|
118 | model = cm.create_file() | |
119 | assert isinstance(model, dict) |
|
119 | assert isinstance(model, dict) | |
120 | self.assertIn('name', model) |
|
120 | self.assertIn('name', model) | |
121 | self.assertIn('path', model) |
|
121 | self.assertIn('path', model) | |
@@ -124,23 +124,23 b' class TestNotebookManager(TestCase):' | |||||
124 |
|
124 | |||
125 | # Test in sub-directory |
|
125 | # Test in sub-directory | |
126 | sub_dir = '/foo/' |
|
126 | sub_dir = '/foo/' | |
127 |
self.make_dir( |
|
127 | self.make_dir(cm.root_dir, 'foo') | |
128 |
model = |
|
128 | model = cm.create_file(None, sub_dir) | |
129 | assert isinstance(model, dict) |
|
129 | assert isinstance(model, dict) | |
130 | self.assertIn('name', model) |
|
130 | self.assertIn('name', model) | |
131 | self.assertIn('path', model) |
|
131 | self.assertIn('path', model) | |
132 | self.assertEqual(model['name'], 'Untitled0.ipynb') |
|
132 | self.assertEqual(model['name'], 'Untitled0.ipynb') | |
133 | self.assertEqual(model['path'], sub_dir.strip('/')) |
|
133 | self.assertEqual(model['path'], sub_dir.strip('/')) | |
134 |
|
134 | |||
135 |
def test_get |
|
135 | def test_get(self): | |
136 |
|
|
136 | cm = self.contents_manager | |
137 | # Create a notebook |
|
137 | # Create a notebook | |
138 |
model = |
|
138 | model = cm.create_file() | |
139 | name = model['name'] |
|
139 | name = model['name'] | |
140 | path = model['path'] |
|
140 | path = model['path'] | |
141 |
|
141 | |||
142 | # Check that we 'get' on the notebook we just created |
|
142 | # Check that we 'get' on the notebook we just created | |
143 |
model2 = |
|
143 | model2 = cm.get_model(name, path) | |
144 | assert isinstance(model2, dict) |
|
144 | assert isinstance(model2, dict) | |
145 | self.assertIn('name', model2) |
|
145 | self.assertIn('name', model2) | |
146 | self.assertIn('path', model2) |
|
146 | self.assertIn('path', model2) | |
@@ -149,66 +149,66 b' class TestNotebookManager(TestCase):' | |||||
149 |
|
149 | |||
150 | # Test in sub-directory |
|
150 | # Test in sub-directory | |
151 | sub_dir = '/foo/' |
|
151 | sub_dir = '/foo/' | |
152 |
self.make_dir( |
|
152 | self.make_dir(cm.root_dir, 'foo') | |
153 |
model = |
|
153 | model = cm.create_file(None, sub_dir) | |
154 |
model2 = |
|
154 | model2 = cm.get_model(name, sub_dir) | |
155 | assert isinstance(model2, dict) |
|
155 | assert isinstance(model2, dict) | |
156 | self.assertIn('name', model2) |
|
156 | self.assertIn('name', model2) | |
157 | self.assertIn('path', model2) |
|
157 | self.assertIn('path', model2) | |
158 | self.assertIn('content', model2) |
|
158 | self.assertIn('content', model2) | |
159 | self.assertEqual(model2['name'], 'Untitled0.ipynb') |
|
159 | self.assertEqual(model2['name'], 'Untitled0.ipynb') | |
160 | self.assertEqual(model2['path'], sub_dir.strip('/')) |
|
160 | self.assertEqual(model2['path'], sub_dir.strip('/')) | |
161 |
|
161 | |||
162 |
def test_update |
|
162 | def test_update(self): | |
163 |
|
|
163 | cm = self.contents_manager | |
164 | # Create a notebook |
|
164 | # Create a notebook | |
165 |
model = |
|
165 | model = cm.create_file() | |
166 | name = model['name'] |
|
166 | name = model['name'] | |
167 | path = model['path'] |
|
167 | path = model['path'] | |
168 |
|
168 | |||
169 | # Change the name in the model for rename |
|
169 | # Change the name in the model for rename | |
170 | model['name'] = 'test.ipynb' |
|
170 | model['name'] = 'test.ipynb' | |
171 |
model = |
|
171 | model = cm.update(model, name, path) | |
172 | assert isinstance(model, dict) |
|
172 | assert isinstance(model, dict) | |
173 | self.assertIn('name', model) |
|
173 | self.assertIn('name', model) | |
174 | self.assertIn('path', model) |
|
174 | self.assertIn('path', model) | |
175 | self.assertEqual(model['name'], 'test.ipynb') |
|
175 | self.assertEqual(model['name'], 'test.ipynb') | |
176 |
|
176 | |||
177 | # Make sure the old name is gone |
|
177 | # Make sure the old name is gone | |
178 |
self.assertRaises(HTTPError, |
|
178 | self.assertRaises(HTTPError, cm.get_model, name, path) | |
179 |
|
179 | |||
180 | # Test in sub-directory |
|
180 | # Test in sub-directory | |
181 | # Create a directory and notebook in that directory |
|
181 | # Create a directory and notebook in that directory | |
182 | sub_dir = '/foo/' |
|
182 | sub_dir = '/foo/' | |
183 |
self.make_dir( |
|
183 | self.make_dir(cm.root_dir, 'foo') | |
184 |
model = |
|
184 | model = cm.create_file(None, sub_dir) | |
185 | name = model['name'] |
|
185 | name = model['name'] | |
186 | path = model['path'] |
|
186 | path = model['path'] | |
187 |
|
187 | |||
188 | # Change the name in the model for rename |
|
188 | # Change the name in the model for rename | |
189 | model['name'] = 'test_in_sub.ipynb' |
|
189 | model['name'] = 'test_in_sub.ipynb' | |
190 |
model = |
|
190 | model = cm.update(model, name, path) | |
191 | assert isinstance(model, dict) |
|
191 | assert isinstance(model, dict) | |
192 | self.assertIn('name', model) |
|
192 | self.assertIn('name', model) | |
193 | self.assertIn('path', model) |
|
193 | self.assertIn('path', model) | |
194 | self.assertEqual(model['name'], 'test_in_sub.ipynb') |
|
194 | self.assertEqual(model['name'], 'test_in_sub.ipynb') | |
195 | self.assertEqual(model['path'], sub_dir.strip('/')) |
|
195 | self.assertEqual(model['path'], sub_dir.strip('/')) | |
196 |
|
196 | |||
197 | # Make sure the old name is gone |
|
197 | # Make sure the old name is gone | |
198 |
self.assertRaises(HTTPError, |
|
198 | self.assertRaises(HTTPError, cm.get_model, name, path) | |
199 |
|
199 | |||
200 |
def test_save |
|
200 | def test_save(self): | |
201 |
|
|
201 | cm = self.contents_manager | |
202 | # Create a notebook |
|
202 | # Create a notebook | |
203 |
model = |
|
203 | model = cm.create_file() | |
204 | name = model['name'] |
|
204 | name = model['name'] | |
205 | path = model['path'] |
|
205 | path = model['path'] | |
206 |
|
206 | |||
207 | # Get the model with 'content' |
|
207 | # Get the model with 'content' | |
208 |
full_model = |
|
208 | full_model = cm.get_model(name, path) | |
209 |
|
209 | |||
210 | # Save the notebook |
|
210 | # Save the notebook | |
211 |
model = |
|
211 | model = cm.save(full_model, name, path) | |
212 | assert isinstance(model, dict) |
|
212 | assert isinstance(model, dict) | |
213 | self.assertIn('name', model) |
|
213 | self.assertIn('name', model) | |
214 | self.assertIn('path', model) |
|
214 | self.assertIn('path', model) | |
@@ -218,103 +218,84 b' class TestNotebookManager(TestCase):' | |||||
218 | # Test in sub-directory |
|
218 | # Test in sub-directory | |
219 | # Create a directory and notebook in that directory |
|
219 | # Create a directory and notebook in that directory | |
220 | sub_dir = '/foo/' |
|
220 | sub_dir = '/foo/' | |
221 |
self.make_dir( |
|
221 | self.make_dir(cm.root_dir, 'foo') | |
222 |
model = |
|
222 | model = cm.create_file(None, sub_dir) | |
223 | name = model['name'] |
|
223 | name = model['name'] | |
224 | path = model['path'] |
|
224 | path = model['path'] | |
225 |
model = |
|
225 | model = cm.get_model(name, path) | |
226 |
|
226 | |||
227 | # Change the name in the model for rename |
|
227 | # Change the name in the model for rename | |
228 |
model = |
|
228 | model = cm.save(model, name, path) | |
229 | assert isinstance(model, dict) |
|
229 | assert isinstance(model, dict) | |
230 | self.assertIn('name', model) |
|
230 | self.assertIn('name', model) | |
231 | self.assertIn('path', model) |
|
231 | self.assertIn('path', model) | |
232 | self.assertEqual(model['name'], 'Untitled0.ipynb') |
|
232 | self.assertEqual(model['name'], 'Untitled0.ipynb') | |
233 | self.assertEqual(model['path'], sub_dir.strip('/')) |
|
233 | self.assertEqual(model['path'], sub_dir.strip('/')) | |
234 |
|
234 | |||
235 |
def test_ |
|
235 | def test_delete(self): | |
236 |
|
|
236 | cm = self.contents_manager | |
237 | # Create a notebook |
|
|||
238 | model = nm.create_notebook() |
|
|||
239 | nm.save_script = True |
|
|||
240 | model = nm.create_notebook() |
|
|||
241 | name = model['name'] |
|
|||
242 | path = model['path'] |
|
|||
243 |
|
||||
244 | # Get the model with 'content' |
|
|||
245 | full_model = nm.get_notebook(name, path) |
|
|||
246 |
|
||||
247 | # Save the notebook |
|
|||
248 | model = nm.save_notebook(full_model, name, path) |
|
|||
249 |
|
||||
250 | # Check that the script was created |
|
|||
251 | py_path = os.path.join(nm.notebook_dir, os.path.splitext(name)[0]+'.py') |
|
|||
252 | assert os.path.exists(py_path), py_path |
|
|||
253 |
|
||||
254 | def test_delete_notebook(self): |
|
|||
255 | nm = self.notebook_manager |
|
|||
256 | # Create a notebook |
|
237 | # Create a notebook | |
257 | nb, name, path = self.new_notebook() |
|
238 | nb, name, path = self.new_notebook() | |
258 |
|
239 | |||
259 | # Delete the notebook |
|
240 | # Delete the notebook | |
260 |
|
|
241 | cm.delete(name, path) | |
261 |
|
242 | |||
262 | # Check that a 'get' on the deleted notebook raises and error |
|
243 | # Check that a 'get' on the deleted notebook raises and error | |
263 |
self.assertRaises(HTTPError, |
|
244 | self.assertRaises(HTTPError, cm.get_model, name, path) | |
264 |
|
245 | |||
265 |
def test_copy |
|
246 | def test_copy(self): | |
266 |
|
|
247 | cm = self.contents_manager | |
267 | path = u'å b' |
|
248 | path = u'å b' | |
268 | name = u'nb √.ipynb' |
|
249 | name = u'nb √.ipynb' | |
269 |
os.mkdir(os.path.join( |
|
250 | os.mkdir(os.path.join(cm.root_dir, path)) | |
270 |
orig = |
|
251 | orig = cm.create_file({'name' : name}, path=path) | |
271 |
|
252 | |||
272 | # copy with unspecified name |
|
253 | # copy with unspecified name | |
273 |
copy = |
|
254 | copy = cm.copy(name, path=path) | |
274 | self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb')) |
|
255 | self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb')) | |
275 |
|
256 | |||
276 | # copy with specified name |
|
257 | # copy with specified name | |
277 |
copy2 = |
|
258 | copy2 = cm.copy(name, u'copy 2.ipynb', path=path) | |
278 | self.assertEqual(copy2['name'], u'copy 2.ipynb') |
|
259 | self.assertEqual(copy2['name'], u'copy 2.ipynb') | |
279 |
|
260 | |||
280 | def test_trust_notebook(self): |
|
261 | def test_trust_notebook(self): | |
281 |
|
|
262 | cm = self.contents_manager | |
282 | nb, name, path = self.new_notebook() |
|
263 | nb, name, path = self.new_notebook() | |
283 |
|
264 | |||
284 |
untrusted = |
|
265 | untrusted = cm.get_model(name, path)['content'] | |
285 |
assert not |
|
266 | assert not cm.notary.check_cells(untrusted) | |
286 |
|
267 | |||
287 | # print(untrusted) |
|
268 | # print(untrusted) | |
288 |
|
|
269 | cm.trust_notebook(name, path) | |
289 |
trusted = |
|
270 | trusted = cm.get_model(name, path)['content'] | |
290 | # print(trusted) |
|
271 | # print(trusted) | |
291 |
assert |
|
272 | assert cm.notary.check_cells(trusted) | |
292 |
|
273 | |||
293 | def test_mark_trusted_cells(self): |
|
274 | def test_mark_trusted_cells(self): | |
294 |
|
|
275 | cm = self.contents_manager | |
295 | nb, name, path = self.new_notebook() |
|
276 | nb, name, path = self.new_notebook() | |
296 |
|
277 | |||
297 |
|
|
278 | cm.mark_trusted_cells(nb, name, path) | |
298 | for cell in nb.worksheets[0].cells: |
|
279 | for cell in nb.worksheets[0].cells: | |
299 | if cell.cell_type == 'code': |
|
280 | if cell.cell_type == 'code': | |
300 | assert not cell.trusted |
|
281 | assert not cell.trusted | |
301 |
|
282 | |||
302 |
|
|
283 | cm.trust_notebook(name, path) | |
303 |
nb = |
|
284 | nb = cm.get_model(name, path)['content'] | |
304 | for cell in nb.worksheets[0].cells: |
|
285 | for cell in nb.worksheets[0].cells: | |
305 | if cell.cell_type == 'code': |
|
286 | if cell.cell_type == 'code': | |
306 | assert cell.trusted |
|
287 | assert cell.trusted | |
307 |
|
288 | |||
308 | def test_check_and_sign(self): |
|
289 | def test_check_and_sign(self): | |
309 |
|
|
290 | cm = self.contents_manager | |
310 | nb, name, path = self.new_notebook() |
|
291 | nb, name, path = self.new_notebook() | |
311 |
|
292 | |||
312 |
|
|
293 | cm.mark_trusted_cells(nb, name, path) | |
313 |
|
|
294 | cm.check_and_sign(nb, name, path) | |
314 |
assert not |
|
295 | assert not cm.notary.check_signature(nb) | |
315 |
|
296 | |||
316 |
|
|
297 | cm.trust_notebook(name, path) | |
317 |
nb = |
|
298 | nb = cm.get_model(name, path)['content'] | |
318 |
|
|
299 | cm.mark_trusted_cells(nb, name, path) | |
319 |
|
|
300 | cm.check_and_sign(nb, name, path) | |
320 |
assert |
|
301 | assert cm.notary.check_signature(nb) |
@@ -27,8 +27,16 b' class MainKernelHandler(IPythonHandler):' | |||||
27 | @web.authenticated |
|
27 | @web.authenticated | |
28 | @json_errors |
|
28 | @json_errors | |
29 | def post(self): |
|
29 | def post(self): | |
|
30 | model = self.get_json_body() | |||
|
31 | if model is None: | |||
|
32 | raise web.HTTPError(400, "No JSON data provided") | |||
|
33 | try: | |||
|
34 | name = model['name'] | |||
|
35 | except KeyError: | |||
|
36 | raise web.HTTPError(400, "Missing field in JSON data: name") | |||
|
37 | ||||
30 | km = self.kernel_manager |
|
38 | km = self.kernel_manager | |
31 | kernel_id = km.start_kernel() |
|
39 | kernel_id = km.start_kernel(kernel_name=name) | |
32 | model = km.kernel_model(kernel_id) |
|
40 | model = km.kernel_model(kernel_id) | |
33 | location = url_path_join(self.base_url, 'api', 'kernels', kernel_id) |
|
41 | location = url_path_join(self.base_url, 'api', 'kernels', kernel_id) | |
34 | self.set_header('Location', url_escape(location)) |
|
42 | self.set_header('Location', url_escape(location)) | |
@@ -76,6 +84,9 b' class KernelActionHandler(IPythonHandler):' | |||||
76 |
|
84 | |||
77 | class ZMQChannelHandler(AuthenticatedZMQStreamHandler): |
|
85 | class ZMQChannelHandler(AuthenticatedZMQStreamHandler): | |
78 |
|
86 | |||
|
87 | def __repr__(self): | |||
|
88 | return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized')) | |||
|
89 | ||||
79 | def create_stream(self): |
|
90 | def create_stream(self): | |
80 | km = self.kernel_manager |
|
91 | km = self.kernel_manager | |
81 | meth = getattr(km, 'connect_%s' % self.channel) |
|
92 | meth = getattr(km, 'connect_%s' % self.channel) | |
@@ -137,6 +148,12 b' class ZMQChannelHandler(AuthenticatedZMQStreamHandler):' | |||||
137 | self.zmq_stream.on_recv(self._on_zmq_reply) |
|
148 | self.zmq_stream.on_recv(self._on_zmq_reply) | |
138 |
|
149 | |||
139 | def on_message(self, msg): |
|
150 | def on_message(self, msg): | |
|
151 | if self.zmq_stream is None: | |||
|
152 | return | |||
|
153 | elif self.zmq_stream.closed(): | |||
|
154 | self.log.info("%s closed, closing websocket.", self) | |||
|
155 | self.close() | |||
|
156 | return | |||
140 | msg = json.loads(msg) |
|
157 | msg = json.loads(msg) | |
141 | self.session.send(self.zmq_stream, msg) |
|
158 | self.session.send(self.zmq_stream, msg) | |
142 |
|
159 |
@@ -72,8 +72,8 b' class MappingKernelManager(MultiKernelManager):' | |||||
72 | os_path = os.path.dirname(os_path) |
|
72 | os_path = os.path.dirname(os_path) | |
73 | return os_path |
|
73 | return os_path | |
74 |
|
74 | |||
75 | def start_kernel(self, kernel_id=None, path=None, **kwargs): |
|
75 | def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs): | |
76 | """Start a kernel for a session an return its kernel_id. |
|
76 | """Start a kernel for a session and return its kernel_id. | |
77 |
|
77 | |||
78 | Parameters |
|
78 | Parameters | |
79 | ---------- |
|
79 | ---------- | |
@@ -84,12 +84,16 b' class MappingKernelManager(MultiKernelManager):' | |||||
84 | path : API path |
|
84 | path : API path | |
85 | The API path (unicode, '/' delimited) for the cwd. |
|
85 | The API path (unicode, '/' delimited) for the cwd. | |
86 | Will be transformed to an OS path relative to root_dir. |
|
86 | Will be transformed to an OS path relative to root_dir. | |
|
87 | kernel_name : str | |||
|
88 | The name identifying which kernel spec to launch. This is ignored if | |||
|
89 | an existing kernel is returned, but it may be checked in the future. | |||
87 | """ |
|
90 | """ | |
88 | if kernel_id is None: |
|
91 | if kernel_id is None: | |
89 | kwargs['extra_arguments'] = self.kernel_argv |
|
92 | kwargs['extra_arguments'] = self.kernel_argv | |
90 | if path is not None: |
|
93 | if path is not None: | |
91 | kwargs['cwd'] = self.cwd_for_path(path) |
|
94 | kwargs['cwd'] = self.cwd_for_path(path) | |
92 |
kernel_id = super(MappingKernelManager, self).start_kernel( |
|
95 | kernel_id = super(MappingKernelManager, self).start_kernel( | |
|
96 | kernel_name=kernel_name, **kwargs) | |||
93 | self.log.info("Kernel started: %s" % kernel_id) |
|
97 | self.log.info("Kernel started: %s" % kernel_id) | |
94 | self.log.debug("Kernel args: %r" % kwargs) |
|
98 | self.log.debug("Kernel args: %r" % kwargs) | |
95 | # register callback for failed auto-restart |
|
99 | # register callback for failed auto-restart | |
@@ -111,7 +115,8 b' class MappingKernelManager(MultiKernelManager):' | |||||
111 | """Return a dictionary of kernel information described in the |
|
115 | """Return a dictionary of kernel information described in the | |
112 | JSON standard model.""" |
|
116 | JSON standard model.""" | |
113 | self._check_kernel_id(kernel_id) |
|
117 | self._check_kernel_id(kernel_id) | |
114 |
model = {"id":kernel_id |
|
118 | model = {"id":kernel_id, | |
|
119 | "name": self._kernels[kernel_id].kernel_name} | |||
115 | return model |
|
120 | return model | |
116 |
|
121 | |||
117 | def list_kernels(self): |
|
122 | def list_kernels(self): |
@@ -1,6 +1,6 b'' | |||||
1 | """Test the kernels service API.""" |
|
1 | """Test the kernels service API.""" | |
2 |
|
2 | |||
3 |
|
3 | import json | ||
4 | import requests |
|
4 | import requests | |
5 |
|
5 | |||
6 | from IPython.html.utils import url_path_join |
|
6 | from IPython.html.utils import url_path_join | |
@@ -30,8 +30,9 b' class KernelAPI(object):' | |||||
30 | def get(self, id): |
|
30 | def get(self, id): | |
31 | return self._req('GET', id) |
|
31 | return self._req('GET', id) | |
32 |
|
32 | |||
33 | def start(self): |
|
33 | def start(self, name='python'): | |
34 | return self._req('POST', '') |
|
34 | body = json.dumps({'name': name}) | |
|
35 | return self._req('POST', '', body) | |||
35 |
|
36 | |||
36 | def shutdown(self, id): |
|
37 | def shutdown(self, id): | |
37 | return self._req('DELETE', id) |
|
38 | return self._req('DELETE', id) | |
@@ -64,11 +65,14 b' class KernelAPITest(NotebookTestBase):' | |||||
64 | self.assertEqual(r.status_code, 201) |
|
65 | self.assertEqual(r.status_code, 201) | |
65 | self.assertIsInstance(kern1, dict) |
|
66 | self.assertIsInstance(kern1, dict) | |
66 |
|
67 | |||
|
68 | self.assertEqual(r.headers['x-frame-options'], "SAMEORIGIN") | |||
|
69 | ||||
67 | # GET request |
|
70 | # GET request | |
68 | r = self.kern_api.list() |
|
71 | r = self.kern_api.list() | |
69 | self.assertEqual(r.status_code, 200) |
|
72 | self.assertEqual(r.status_code, 200) | |
70 | assert isinstance(r.json(), list) |
|
73 | assert isinstance(r.json(), list) | |
71 | self.assertEqual(r.json()[0]['id'], kern1['id']) |
|
74 | self.assertEqual(r.json()[0]['id'], kern1['id']) | |
|
75 | self.assertEqual(r.json()[0]['name'], kern1['name']) | |||
72 |
|
76 | |||
73 | # create another kernel and check that they both are added to the |
|
77 | # create another kernel and check that they both are added to the | |
74 | # list of kernels from a GET request |
|
78 | # list of kernels from a GET request | |
@@ -89,6 +93,7 b' class KernelAPITest(NotebookTestBase):' | |||||
89 | self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id']) |
|
93 | self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id']) | |
90 | rekern = r.json() |
|
94 | rekern = r.json() | |
91 | self.assertEqual(rekern['id'], kern2['id']) |
|
95 | self.assertEqual(rekern['id'], kern2['id']) | |
|
96 | self.assertEqual(rekern['name'], kern2['name']) | |||
92 |
|
97 | |||
93 | def test_kernel_handler(self): |
|
98 | def test_kernel_handler(self): | |
94 | # GET kernel with given id |
|
99 | # GET kernel with given id |
@@ -7,6 +7,8 b' from tornado import web' | |||||
7 |
|
7 | |||
8 | from ...base.handlers import IPythonHandler, json_errors |
|
8 | from ...base.handlers import IPythonHandler, json_errors | |
9 |
|
9 | |||
|
10 | from IPython.kernel.kernelspec import _pythonfirst | |||
|
11 | ||||
10 |
|
12 | |||
11 | class MainKernelSpecHandler(IPythonHandler): |
|
13 | class MainKernelSpecHandler(IPythonHandler): | |
12 | SUPPORTED_METHODS = ('GET',) |
|
14 | SUPPORTED_METHODS = ('GET',) | |
@@ -16,7 +18,7 b' class MainKernelSpecHandler(IPythonHandler):' | |||||
16 | def get(self): |
|
18 | def get(self): | |
17 | ksm = self.kernel_spec_manager |
|
19 | ksm = self.kernel_spec_manager | |
18 | results = [] |
|
20 | results = [] | |
19 | for kernel_name in ksm.find_kernel_specs(): |
|
21 | for kernel_name in sorted(ksm.find_kernel_specs(), key=_pythonfirst): | |
20 | d = ksm.get_kernel_spec(kernel_name).to_dict() |
|
22 | d = ksm.get_kernel_spec(kernel_name).to_dict() | |
21 | d['name'] = kernel_name |
|
23 | d['name'] = kernel_name | |
22 | results.append(d) |
|
24 | results.append(d) |
@@ -1,20 +1,7 b'' | |||||
1 | """Tornado handlers for the sessions web service. |
|
1 | """Tornado handlers for the sessions web service.""" | |
2 |
|
2 | |||
3 | Authors: |
|
3 | # Copyright (c) IPython Development Team. | |
4 |
|
4 | # Distributed under the terms of the Modified BSD License. | ||
5 | * Zach Sailer |
|
|||
6 | """ |
|
|||
7 |
|
||||
8 | #----------------------------------------------------------------------------- |
|
|||
9 | # Copyright (C) 2013 The IPython Development Team |
|
|||
10 | # |
|
|||
11 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
12 | # the file COPYING, distributed as part of this software. |
|
|||
13 | #----------------------------------------------------------------------------- |
|
|||
14 |
|
||||
15 | #----------------------------------------------------------------------------- |
|
|||
16 | # Imports |
|
|||
17 | #----------------------------------------------------------------------------- |
|
|||
18 |
|
5 | |||
19 | import json |
|
6 | import json | |
20 |
|
7 | |||
@@ -24,10 +11,6 b' from ...base.handlers import IPythonHandler, json_errors' | |||||
24 | from IPython.utils.jsonutil import date_default |
|
11 | from IPython.utils.jsonutil import date_default | |
25 | from IPython.html.utils import url_path_join, url_escape |
|
12 | from IPython.html.utils import url_path_join, url_escape | |
26 |
|
13 | |||
27 | #----------------------------------------------------------------------------- |
|
|||
28 | # Session web service handlers |
|
|||
29 | #----------------------------------------------------------------------------- |
|
|||
30 |
|
||||
31 |
|
14 | |||
32 | class SessionRootHandler(IPythonHandler): |
|
15 | class SessionRootHandler(IPythonHandler): | |
33 |
|
16 | |||
@@ -45,27 +28,30 b' class SessionRootHandler(IPythonHandler):' | |||||
45 | # Creates a new session |
|
28 | # Creates a new session | |
46 | #(unless a session already exists for the named nb) |
|
29 | #(unless a session already exists for the named nb) | |
47 | sm = self.session_manager |
|
30 | sm = self.session_manager | |
48 |
|
|
31 | cm = self.contents_manager | |
49 | km = self.kernel_manager |
|
32 | km = self.kernel_manager | |
|
33 | ||||
50 | model = self.get_json_body() |
|
34 | model = self.get_json_body() | |
51 | if model is None: |
|
35 | if model is None: | |
52 | raise web.HTTPError(400, "No JSON data provided") |
|
36 | raise web.HTTPError(400, "No JSON data provided") | |
53 | try: |
|
37 | try: | |
54 | name = model['notebook']['name'] |
|
38 | name = model['notebook']['name'] | |
55 | except KeyError: |
|
39 | except KeyError: | |
56 | raise web.HTTPError(400, "Missing field in JSON data: name") |
|
40 | raise web.HTTPError(400, "Missing field in JSON data: notebook.name") | |
57 | try: |
|
41 | try: | |
58 | path = model['notebook']['path'] |
|
42 | path = model['notebook']['path'] | |
59 | except KeyError: |
|
43 | except KeyError: | |
60 | raise web.HTTPError(400, "Missing field in JSON data: path") |
|
44 | raise web.HTTPError(400, "Missing field in JSON data: notebook.path") | |
|
45 | try: | |||
|
46 | kernel_name = model['kernel']['name'] | |||
|
47 | except KeyError: | |||
|
48 | raise web.HTTPError(400, "Missing field in JSON data: kernel.name") | |||
|
49 | ||||
61 | # Check to see if session exists |
|
50 | # Check to see if session exists | |
62 | if sm.session_exists(name=name, path=path): |
|
51 | if sm.session_exists(name=name, path=path): | |
63 | model = sm.get_session(name=name, path=path) |
|
52 | model = sm.get_session(name=name, path=path) | |
64 | else: |
|
53 | else: | |
65 | # allow nbm to specify kernels cwd |
|
54 | model = sm.create_session(name=name, path=path, kernel_name=kernel_name) | |
66 | kernel_path = nbm.get_kernel_path(name=name, path=path) |
|
|||
67 | kernel_id = km.start_kernel(path=kernel_path) |
|
|||
68 | model = sm.create_session(name=name, path=path, kernel_id=kernel_id) |
|
|||
69 | location = url_path_join(self.base_url, 'api', 'sessions', model['id']) |
|
55 | location = url_path_join(self.base_url, 'api', 'sessions', model['id']) | |
70 | self.set_header('Location', url_escape(location)) |
|
56 | self.set_header('Location', url_escape(location)) | |
71 | self.set_status(201) |
|
57 | self.set_status(201) | |
@@ -108,10 +94,7 b' class SessionHandler(IPythonHandler):' | |||||
108 | def delete(self, session_id): |
|
94 | def delete(self, session_id): | |
109 | # Deletes the session with given session_id |
|
95 | # Deletes the session with given session_id | |
110 | sm = self.session_manager |
|
96 | sm = self.session_manager | |
111 | km = self.kernel_manager |
|
|||
112 | session = sm.get_session(session_id=session_id) |
|
|||
113 | sm.delete_session(session_id) |
|
97 | sm.delete_session(session_id) | |
114 | km.shutdown_kernel(session['kernel']['id']) |
|
|||
115 | self.set_status(204) |
|
98 | self.set_status(204) | |
116 | self.finish() |
|
99 | self.finish() | |
117 |
|
100 |
@@ -23,12 +23,16 b' from tornado import web' | |||||
23 |
|
23 | |||
24 | from IPython.config.configurable import LoggingConfigurable |
|
24 | from IPython.config.configurable import LoggingConfigurable | |
25 | from IPython.utils.py3compat import unicode_type |
|
25 | from IPython.utils.py3compat import unicode_type | |
|
26 | from IPython.utils.traitlets import Instance | |||
26 |
|
27 | |||
27 | #----------------------------------------------------------------------------- |
|
28 | #----------------------------------------------------------------------------- | |
28 | # Classes |
|
29 | # Classes | |
29 | #----------------------------------------------------------------------------- |
|
30 | #----------------------------------------------------------------------------- | |
30 |
|
31 | |||
31 | class SessionManager(LoggingConfigurable): |
|
32 | class SessionManager(LoggingConfigurable): | |
|
33 | ||||
|
34 | kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager') | |||
|
35 | contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=()) | |||
32 |
|
36 | |||
33 | # Session database initialized below |
|
37 | # Session database initialized below | |
34 | _cursor = None |
|
38 | _cursor = None | |
@@ -69,10 +73,15 b' class SessionManager(LoggingConfigurable):' | |||||
69 | "Create a uuid for a new session" |
|
73 | "Create a uuid for a new session" | |
70 | return unicode_type(uuid.uuid4()) |
|
74 | return unicode_type(uuid.uuid4()) | |
71 |
|
75 | |||
72 |
def create_session(self, name=None, path=None, kernel_ |
|
76 | def create_session(self, name=None, path=None, kernel_name='python'): | |
73 | """Creates a session and returns its model""" |
|
77 | """Creates a session and returns its model""" | |
74 | session_id = self.new_session_id() |
|
78 | session_id = self.new_session_id() | |
75 | return self.save_session(session_id, name=name, path=path, kernel_id=kernel_id) |
|
79 | # allow nbm to specify kernels cwd | |
|
80 | kernel_path = self.contents_manager.get_kernel_path(name=name, path=path) | |||
|
81 | kernel_id = self.kernel_manager.start_kernel(path=kernel_path, | |||
|
82 | kernel_name=kernel_name) | |||
|
83 | return self.save_session(session_id, name=name, path=path, | |||
|
84 | kernel_id=kernel_id) | |||
76 |
|
85 | |||
77 | def save_session(self, session_id, name=None, path=None, kernel_id=None): |
|
86 | def save_session(self, session_id, name=None, path=None, kernel_id=None): | |
78 | """Saves the items for the session with the given session_id |
|
87 | """Saves the items for the session with the given session_id | |
@@ -170,8 +179,7 b' class SessionManager(LoggingConfigurable):' | |||||
170 | query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets)) |
|
179 | query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets)) | |
171 | self.cursor.execute(query, list(kwargs.values()) + [session_id]) |
|
180 | self.cursor.execute(query, list(kwargs.values()) + [session_id]) | |
172 |
|
181 | |||
173 | @staticmethod |
|
182 | def row_factory(self, cursor, row): | |
174 | def row_factory(cursor, row): |
|
|||
175 | """Takes sqlite database session row and turns it into a dictionary""" |
|
183 | """Takes sqlite database session row and turns it into a dictionary""" | |
176 | row = sqlite3.Row(cursor, row) |
|
184 | row = sqlite3.Row(cursor, row) | |
177 | model = { |
|
185 | model = { | |
@@ -180,9 +188,7 b' class SessionManager(LoggingConfigurable):' | |||||
180 | 'name': row['name'], |
|
188 | 'name': row['name'], | |
181 | 'path': row['path'] |
|
189 | 'path': row['path'] | |
182 | }, |
|
190 | }, | |
183 | 'kernel': { |
|
191 | 'kernel': self.kernel_manager.kernel_model(row['kernel_id']) | |
184 | 'id': row['kernel_id'], |
|
|||
185 | } |
|
|||
186 | } |
|
192 | } | |
187 | return model |
|
193 | return model | |
188 |
|
194 | |||
@@ -195,5 +201,6 b' class SessionManager(LoggingConfigurable):' | |||||
195 | def delete_session(self, session_id): |
|
201 | def delete_session(self, session_id): | |
196 | """Deletes the row in the session database with given session_id""" |
|
202 | """Deletes the row in the session database with given session_id""" | |
197 | # Check that session exists before deleting |
|
203 | # Check that session exists before deleting | |
198 | self.get_session(session_id=session_id) |
|
204 | session = self.get_session(session_id=session_id) | |
|
205 | self.kernel_manager.shutdown_kernel(session['kernel']['id']) | |||
199 | self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,)) |
|
206 | self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,)) |
@@ -5,79 +5,101 b' from unittest import TestCase' | |||||
5 | from tornado import web |
|
5 | from tornado import web | |
6 |
|
6 | |||
7 | from ..sessionmanager import SessionManager |
|
7 | from ..sessionmanager import SessionManager | |
|
8 | from IPython.html.services.kernels.kernelmanager import MappingKernelManager | |||
|
9 | ||||
|
10 | class DummyKernel(object): | |||
|
11 | def __init__(self, kernel_name='python'): | |||
|
12 | self.kernel_name = kernel_name | |||
|
13 | ||||
|
14 | class DummyMKM(MappingKernelManager): | |||
|
15 | """MappingKernelManager interface that doesn't start kernels, for testing""" | |||
|
16 | def __init__(self, *args, **kwargs): | |||
|
17 | super(DummyMKM, self).__init__(*args, **kwargs) | |||
|
18 | self.id_letters = iter(u'ABCDEFGHIJK') | |||
|
19 | ||||
|
20 | def _new_id(self): | |||
|
21 | return next(self.id_letters) | |||
|
22 | ||||
|
23 | def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs): | |||
|
24 | kernel_id = kernel_id or self._new_id() | |||
|
25 | self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name) | |||
|
26 | return kernel_id | |||
|
27 | ||||
|
28 | def shutdown_kernel(self, kernel_id, now=False): | |||
|
29 | del self._kernels[kernel_id] | |||
8 |
|
30 | |||
9 | class TestSessionManager(TestCase): |
|
31 | class TestSessionManager(TestCase): | |
10 |
|
32 | |||
11 | def test_get_session(self): |
|
33 | def test_get_session(self): | |
12 | sm = SessionManager() |
|
34 | sm = SessionManager(kernel_manager=DummyMKM()) | |
13 | session_id = sm.new_session_id() |
|
35 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
14 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') |
|
36 | kernel_name='bar')['id'] | |
15 | model = sm.get_session(session_id=session_id) |
|
37 | model = sm.get_session(session_id=session_id) | |
16 | expected = {'id':session_id, 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}} |
|
38 | expected = {'id':session_id, | |
|
39 | 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, | |||
|
40 | 'kernel': {'id':u'A', 'name': 'bar'}} | |||
17 | self.assertEqual(model, expected) |
|
41 | self.assertEqual(model, expected) | |
18 |
|
42 | |||
19 | def test_bad_get_session(self): |
|
43 | def test_bad_get_session(self): | |
20 | # Should raise error if a bad key is passed to the database. |
|
44 | # Should raise error if a bad key is passed to the database. | |
21 | sm = SessionManager() |
|
45 | sm = SessionManager(kernel_manager=DummyMKM()) | |
22 | session_id = sm.new_session_id() |
|
46 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
23 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') |
|
47 | kernel_name='foo')['id'] | |
24 | self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword |
|
48 | self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword | |
25 |
|
49 | |||
26 | def test_list_sessions(self): |
|
50 | def test_list_sessions(self): | |
27 | sm = SessionManager() |
|
51 | sm = SessionManager(kernel_manager=DummyMKM()) | |
28 | session_id1 = sm.new_session_id() |
|
52 | sessions = [ | |
29 | session_id2 = sm.new_session_id() |
|
53 | sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'), | |
30 | session_id3 = sm.new_session_id() |
|
54 | sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'), | |
31 |
sm. |
|
55 | sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'), | |
32 | sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678') |
|
56 | ] | |
33 | sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678') |
|
|||
34 | sessions = sm.list_sessions() |
|
57 | sessions = sm.list_sessions() | |
35 |
expected = [{'id':session |
|
58 | expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb', | |
36 |
'path': u'/path/to/1/'}, 'kernel':{'id':u' |
|
59 | 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}}, | |
37 |
{'id':session |
|
60 | {'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb', | |
38 |
'path': u'/path/to/2/'}, 'kernel':{'id':u' |
|
61 | 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}}, | |
39 |
{'id':session |
|
62 | {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb', | |
40 |
'path': u'/path/to/3/'}, 'kernel':{'id':u' |
|
63 | 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}] | |
41 | self.assertEqual(sessions, expected) |
|
64 | self.assertEqual(sessions, expected) | |
42 |
|
65 | |||
43 | def test_update_session(self): |
|
66 | def test_update_session(self): | |
44 | sm = SessionManager() |
|
67 | sm = SessionManager(kernel_manager=DummyMKM()) | |
45 | session_id = sm.new_session_id() |
|
68 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
46 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id=None) |
|
69 | kernel_name='julia')['id'] | |
47 | sm.update_session(session_id, kernel_id='5678') |
|
|||
48 | sm.update_session(session_id, name='new_name.ipynb') |
|
70 | sm.update_session(session_id, name='new_name.ipynb') | |
49 | model = sm.get_session(session_id=session_id) |
|
71 | model = sm.get_session(session_id=session_id) | |
50 | expected = {'id':session_id, 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}} |
|
72 | expected = {'id':session_id, | |
|
73 | 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, | |||
|
74 | 'kernel':{'id':u'A', 'name':'julia'}} | |||
51 | self.assertEqual(model, expected) |
|
75 | self.assertEqual(model, expected) | |
52 |
|
76 | |||
53 | def test_bad_update_session(self): |
|
77 | def test_bad_update_session(self): | |
54 | # try to update a session with a bad keyword ~ raise error |
|
78 | # try to update a session with a bad keyword ~ raise error | |
55 | sm = SessionManager() |
|
79 | sm = SessionManager(kernel_manager=DummyMKM()) | |
56 | session_id = sm.new_session_id() |
|
80 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
57 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') |
|
81 | kernel_name='ir')['id'] | |
58 | self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword |
|
82 | self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword | |
59 |
|
83 | |||
60 | def test_delete_session(self): |
|
84 | def test_delete_session(self): | |
61 | sm = SessionManager() |
|
85 | sm = SessionManager(kernel_manager=DummyMKM()) | |
62 | session_id1 = sm.new_session_id() |
|
86 | sessions = [ | |
63 | session_id2 = sm.new_session_id() |
|
87 | sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'), | |
64 | session_id3 = sm.new_session_id() |
|
88 | sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'), | |
65 |
sm. |
|
89 | sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'), | |
66 | sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678') |
|
90 | ] | |
67 | sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678') |
|
91 | sm.delete_session(sessions[1]['id']) | |
68 |
sm. |
|
92 | new_sessions = sm.list_sessions() | |
69 | sessions = sm.list_sessions() |
|
93 | expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb', | |
70 | expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb', |
|
94 | 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}}, | |
71 | 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}}, |
|
95 | {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb', | |
72 | {'id':session_id3, 'notebook':{'name':u'test3.ipynb', |
|
96 | 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}] | |
73 | 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}] |
|
97 | self.assertEqual(new_sessions, expected) | |
74 | self.assertEqual(sessions, expected) |
|
|||
75 |
|
98 | |||
76 | def test_bad_delete_session(self): |
|
99 | def test_bad_delete_session(self): | |
77 | # try to delete a session that doesn't exist ~ raise error |
|
100 | # try to delete a session that doesn't exist ~ raise error | |
78 | sm = SessionManager() |
|
101 | sm = SessionManager(kernel_manager=DummyMKM()) | |
79 | session_id = sm.new_session_id() |
|
102 | sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python') | |
80 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') |
|
|||
81 | self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword |
|
103 | self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword | |
82 | self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant |
|
104 | self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant | |
83 |
|
105 |
@@ -37,8 +37,9 b' class SessionAPI(object):' | |||||
37 | def get(self, id): |
|
37 | def get(self, id): | |
38 | return self._req('GET', id) |
|
38 | return self._req('GET', id) | |
39 |
|
39 | |||
40 | def create(self, name, path): |
|
40 | def create(self, name, path, kernel_name='python'): | |
41 |
body = json.dumps({'notebook': {'name':name, 'path':path} |
|
41 | body = json.dumps({'notebook': {'name':name, 'path':path}, | |
|
42 | 'kernel': {'name': kernel_name}}) | |||
42 | return self._req('POST', '', body) |
|
43 | return self._req('POST', '', body) | |
43 |
|
44 | |||
44 | def modify(self, id, name, path): |
|
45 | def modify(self, id, name, path): |
@@ -1,21 +1,12 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2008-2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | var ipython = ipython || {}; | |
9 | // On document ready |
|
5 | require(['base/js/page'], function(page) { | |
10 | //============================================================================ |
|
6 | var page_instance = new page.Page(); | |
11 |
|
||||
12 |
|
||||
13 | $(document).ready(function () { |
|
|||
14 |
|
||||
15 | IPython.page = new IPython.Page(); |
|
|||
16 | $('button#login_submit').addClass("btn btn-default"); |
|
7 | $('button#login_submit').addClass("btn btn-default"); | |
17 |
|
|
8 | page_instance.show(); | |
18 | $('input#password_input').focus(); |
|
9 | $('input#password_input').focus(); | |
19 |
|
10 | |||
|
11 | ipython.page = page_instance; | |||
20 | }); |
|
12 | }); | |
21 |
|
@@ -1,43 +1,35 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2008-2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
3 | ||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
4 | define([ | |
5 | // the file COPYING, distributed as part of this software. |
|
5 | 'base/js/namespace', | |
6 | //---------------------------------------------------------------------------- |
|
6 | 'base/js/utils', | |
7 |
|
7 | 'jquery', | ||
8 | //============================================================================ |
|
8 | ], function(IPython, utils, $){ | |
9 | // Login button |
|
|||
10 | //============================================================================ |
|
|||
11 |
|
||||
12 | var IPython = (function (IPython) { |
|
|||
13 | "use strict"; |
|
9 | "use strict"; | |
14 |
|
10 | |||
15 | var LoginWidget = function (selector, options) { |
|
11 | var LoginWidget = function (selector, options) { | |
16 | options = options || {}; |
|
12 | options = options || {}; | |
17 |
this.base_url = options.base_url || |
|
13 | this.base_url = options.base_url || utils.get_body_data("baseUrl"); | |
18 | this.selector = selector; |
|
14 | this.selector = selector; | |
19 | if (this.selector !== undefined) { |
|
15 | if (this.selector !== undefined) { | |
20 | this.element = $(selector); |
|
16 | this.element = $(selector); | |
21 | this.style(); |
|
|||
22 | this.bind_events(); |
|
17 | this.bind_events(); | |
23 | } |
|
18 | } | |
24 | }; |
|
19 | }; | |
25 |
|
20 | |||
26 | LoginWidget.prototype.style = function () { |
|
|||
27 | this.element.find("button").addClass("btn btn-default btn-sm"); |
|
|||
28 | }; |
|
|||
29 |
|
21 | |||
30 |
|
22 | |||
31 | LoginWidget.prototype.bind_events = function () { |
|
23 | LoginWidget.prototype.bind_events = function () { | |
32 | var that = this; |
|
24 | var that = this; | |
33 | this.element.find("button#logout").click(function () { |
|
25 | this.element.find("button#logout").click(function () { | |
34 |
window.location = |
|
26 | window.location = utils.url_join_encode( | |
35 | that.base_url, |
|
27 | that.base_url, | |
36 | "logout" |
|
28 | "logout" | |
37 | ); |
|
29 | ); | |
38 | }); |
|
30 | }); | |
39 | this.element.find("button#login").click(function () { |
|
31 | this.element.find("button#login").click(function () { | |
40 |
window.location = |
|
32 | window.location = utils.url_join_encode( | |
41 | that.base_url, |
|
33 | that.base_url, | |
42 | "login" |
|
34 | "login" | |
43 | ); |
|
35 | ); | |
@@ -47,6 +39,5 b' var IPython = (function (IPython) {' | |||||
47 | // Set module variables |
|
39 | // Set module variables | |
48 | IPython.LoginWidget = LoginWidget; |
|
40 | IPython.LoginWidget = LoginWidget; | |
49 |
|
41 | |||
50 | return IPython; |
|
42 | return {'LoginWidget': LoginWidget}; | |
51 |
|
43 | }); | ||
52 | }(IPython)); |
|
@@ -1,20 +1,10 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2008-2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | var ipython = ipython || {}; | |
9 | // On document ready |
|
5 | require(['base/js/page'], function(page) { | |
10 | //============================================================================ |
|
6 | var page_instance = new page.Page(); | |
11 |
|
7 | page_instance.show(); | ||
12 |
|
||||
13 | $(document).ready(function () { |
|
|||
14 |
|
||||
15 | IPython.page = new IPython.Page(); |
|
|||
16 | $('#ipython-main-app').addClass('border-box-sizing'); |
|
|||
17 | IPython.page.show(); |
|
|||
18 |
|
8 | |||
|
9 | ipython.page = page_instance; | |||
19 | }); |
|
10 | }); | |
20 |
|
@@ -1,2 +1,7 b'' | |||||
|
1 | /*! | |||
|
2 | * | |||
|
3 | * IPython auth | |||
|
4 | * | |||
|
5 | */ | |||
1 | @import "login.less"; |
|
6 | @import "login.less"; | |
2 | @import "logout.less"; No newline at end of file |
|
7 | @import "logout.less"; |
@@ -1,20 +1,14 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2013 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | define([ | |
9 | // Utility for modal dialogs with bootstrap |
|
5 | 'base/js/namespace', | |
10 | //============================================================================ |
|
6 | 'jquery', | |
11 |
|
7 | ], function(IPython, $) { | ||
12 | IPython.namespace('IPython.dialog'); |
|
|||
13 |
|
||||
14 | IPython.dialog = (function (IPython) { |
|
|||
15 | "use strict"; |
|
8 | "use strict"; | |
16 |
|
9 | |||
17 | var modal = function (options) { |
|
10 | var modal = function (options) { | |
|
11 | ||||
18 | var modal = $("<div/>") |
|
12 | var modal = $("<div/>") | |
19 | .addClass("modal") |
|
13 | .addClass("modal") | |
20 | .addClass("fade") |
|
14 | .addClass("fade") | |
@@ -79,26 +73,28 b' IPython.dialog = (function (IPython) {' | |||||
79 | }); |
|
73 | }); | |
80 | } |
|
74 | } | |
81 | modal.on("hidden.bs.modal", function () { |
|
75 | modal.on("hidden.bs.modal", function () { | |
82 |
if ( |
|
76 | if (options.notebook) { | |
83 |
var cell = |
|
77 | var cell = options.notebook.get_selected_cell(); | |
84 | if (cell) cell.select(); |
|
78 | if (cell) cell.select(); | |
85 | IPython.keyboard_manager.enable(); |
|
79 | } | |
86 |
|
|
80 | if (options.keyboard_manager) { | |
|
81 | options.keyboard_manager.enable(); | |||
|
82 | options.keyboard_manager.command_mode(); | |||
87 | } |
|
83 | } | |
88 | }); |
|
84 | }); | |
89 |
|
85 | |||
90 |
if ( |
|
86 | if (options.keyboard_manager) { | |
91 |
|
|
87 | options.keyboard_manager.disable(); | |
92 | } |
|
88 | } | |
93 |
|
89 | |||
94 | return modal.modal(options); |
|
90 | return modal.modal(options); | |
95 | }; |
|
91 | }; | |
96 |
|
92 | |||
97 |
var edit_metadata = function ( |
|
93 | var edit_metadata = function (options) { | |
98 | name = name || "Cell"; |
|
94 | options.name = options.name || "Cell"; | |
99 | var error_div = $('<div/>').css('color', 'red'); |
|
95 | var error_div = $('<div/>').css('color', 'red'); | |
100 | var message = |
|
96 | var message = | |
101 | "Manually edit the JSON below to manipulate the metadata for this " + name + "." + |
|
97 | "Manually edit the JSON below to manipulate the metadata for this " + options.name + "." + | |
102 | " We recommend putting custom metadata attributes in an appropriately named sub-structure," + |
|
98 | " We recommend putting custom metadata attributes in an appropriately named sub-structure," + | |
103 | " so they don't conflict with those of others."; |
|
99 | " so they don't conflict with those of others."; | |
104 |
|
100 | |||
@@ -106,7 +102,7 b' IPython.dialog = (function (IPython) {' | |||||
106 | .attr('rows', '13') |
|
102 | .attr('rows', '13') | |
107 | .attr('cols', '80') |
|
103 | .attr('cols', '80') | |
108 | .attr('name', 'metadata') |
|
104 | .attr('name', 'metadata') | |
109 | .text(JSON.stringify(md || {}, null, 2)); |
|
105 | .text(JSON.stringify(options.md || {}, null, 2)); | |
110 |
|
106 | |||
111 | var dialogform = $('<div/>').attr('title', 'Edit the metadata') |
|
107 | var dialogform = $('<div/>').attr('title', 'Edit the metadata') | |
112 | .append( |
|
108 | .append( | |
@@ -128,8 +124,8 b' IPython.dialog = (function (IPython) {' | |||||
128 | autoIndent: true, |
|
124 | autoIndent: true, | |
129 | mode: 'application/json', |
|
125 | mode: 'application/json', | |
130 | }); |
|
126 | }); | |
131 |
var modal = |
|
127 | var modal_obj = modal({ | |
132 | title: "Edit " + name + " Metadata", |
|
128 | title: "Edit " + options.name + " Metadata", | |
133 | body: dialogform, |
|
129 | body: dialogform, | |
134 | buttons: { |
|
130 | buttons: { | |
135 | OK: { class : "btn-primary", |
|
131 | OK: { class : "btn-primary", | |
@@ -143,19 +139,25 b' IPython.dialog = (function (IPython) {' | |||||
143 | error_div.text('WARNING: Could not save invalid JSON.'); |
|
139 | error_div.text('WARNING: Could not save invalid JSON.'); | |
144 | return false; |
|
140 | return false; | |
145 | } |
|
141 | } | |
146 | callback(new_md); |
|
142 | options.callback(new_md); | |
147 | } |
|
143 | } | |
148 | }, |
|
144 | }, | |
149 | Cancel: {} |
|
145 | Cancel: {} | |
150 | } |
|
146 | }, | |
|
147 | notebook: options.notebook, | |||
|
148 | keyboard_manager: options.keyboard_manager, | |||
151 | }); |
|
149 | }); | |
152 |
|
150 | |||
153 | modal.on('shown.bs.modal', function(){ editor.refresh(); }); |
|
151 | modal_obj.on('shown.bs.modal', function(){ editor.refresh(); }); | |
154 | }; |
|
152 | }; | |
155 |
|
153 | |||
156 | return { |
|
154 | var dialog = { | |
157 | modal : modal, |
|
155 | modal : modal, | |
158 | edit_metadata : edit_metadata, |
|
156 | edit_metadata : edit_metadata, | |
159 | }; |
|
157 | }; | |
160 |
|
158 | |||
161 | }(IPython)); |
|
159 | // Backwards compatability. | |
|
160 | IPython.dialog = dialog; | |||
|
161 | ||||
|
162 | return dialog; | |||
|
163 | }); |
@@ -1,32 +1,24 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2008-2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
||||
8 | //============================================================================ |
|
|||
9 | // Events |
|
|||
10 | //============================================================================ |
|
|||
11 |
|
3 | |||
12 | // Give us an object to bind all events to. This object should be created |
|
4 | // Give us an object to bind all events to. This object should be created | |
13 | // before all other objects so it exists when others register event handlers. |
|
5 | // before all other objects so it exists when others register event handlers. | |
14 |
// To |
|
6 | // To register an event handler: | |
15 | // $([IPython.events]).trigger('event.Namespace'); |
|
7 | // | |
16 | // To handle it: |
|
8 | // require(['base/js/events'], function (events) { | |
17 |
// |
|
9 | // events.on("event.Namespace", function () { do_stuff(); }); | |
|
10 | // }); | |||
18 |
|
11 | |||
19 | var IPython = (function (IPython) { |
|
12 | define(['base/js/namespace', 'jquery'], function(IPython, $) { | |
20 | "use strict"; |
|
13 | "use strict"; | |
21 |
|
14 | |||
22 | var utils = IPython.utils; |
|
|||
23 |
|
||||
24 | var Events = function () {}; |
|
15 | var Events = function () {}; | |
25 |
|
16 | |||
|
17 | var events = new Events(); | |||
|
18 | ||||
|
19 | // Backwards compatability. | |||
26 | IPython.Events = Events; |
|
20 | IPython.Events = Events; | |
27 |
IPython.events = |
|
21 | IPython.events = events; | |
28 |
|
22 | |||
29 |
return |
|
23 | return $([events]); | |
30 |
|
24 | }); | ||
31 | }(IPython)); |
|
|||
32 |
|
@@ -1,19 +1,14 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | define([ | |
9 | // Keyboard management |
|
5 | 'base/js/namespace', | |
10 | //============================================================================ |
|
6 | 'jquery', | |
11 |
|
7 | 'base/js/utils', | ||
12 | IPython.namespace('IPython.keyboard'); |
|
8 | ], function(IPython, $, utils) { | |
13 |
|
||||
14 | IPython.keyboard = (function (IPython) { |
|
|||
15 | "use strict"; |
|
9 | "use strict"; | |
16 |
|
10 | |||
|
11 | ||||
17 | // Setup global keycodes and inverse keycodes. |
|
12 | // Setup global keycodes and inverse keycodes. | |
18 |
|
13 | |||
19 | // See http://unixpapa.com/js/key.html for a complete description. The short of |
|
14 | // See http://unixpapa.com/js/key.html for a complete description. The short of | |
@@ -51,8 +46,8 b' IPython.keyboard = (function (IPython) {' | |||||
51 | '; :': 186, '= +': 187, '- _': 189 |
|
46 | '; :': 186, '= +': 187, '- _': 189 | |
52 | }; |
|
47 | }; | |
53 |
|
48 | |||
54 |
var browser = |
|
49 | var browser = utils.browser[0]; | |
55 |
var platform = |
|
50 | var platform = utils.platform; | |
56 |
|
51 | |||
57 | if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') { |
|
52 | if (browser === 'Firefox' || browser === 'Opera' || browser === 'Netscape') { | |
58 | $.extend(_keycodes, _mozilla_keycodes); |
|
53 | $.extend(_keycodes, _mozilla_keycodes); | |
@@ -130,18 +125,19 b' IPython.keyboard = (function (IPython) {' | |||||
130 |
|
125 | |||
131 | // Shortcut manager class |
|
126 | // Shortcut manager class | |
132 |
|
127 | |||
133 | var ShortcutManager = function (delay) { |
|
128 | var ShortcutManager = function (delay, events) { | |
134 | this._shortcuts = {}; |
|
129 | this._shortcuts = {}; | |
135 | this._counts = {}; |
|
130 | this._counts = {}; | |
136 | this._timers = {}; |
|
131 | this._timers = {}; | |
137 | this.delay = delay || 800; // delay in milliseconds |
|
132 | this.delay = delay || 800; // delay in milliseconds | |
|
133 | this.events = events; | |||
138 | }; |
|
134 | }; | |
139 |
|
135 | |||
140 | ShortcutManager.prototype.help = function () { |
|
136 | ShortcutManager.prototype.help = function () { | |
141 | var help = []; |
|
137 | var help = []; | |
142 | for (var shortcut in this._shortcuts) { |
|
138 | for (var shortcut in this._shortcuts) { | |
143 |
var help_string = this._shortcuts[shortcut] |
|
139 | var help_string = this._shortcuts[shortcut].help; | |
144 |
var help_index = this._shortcuts[shortcut] |
|
140 | var help_index = this._shortcuts[shortcut].help_index; | |
145 | if (help_string) { |
|
141 | if (help_string) { | |
146 | if (platform === 'MacOS') { |
|
142 | if (platform === 'MacOS') { | |
147 | shortcut = shortcut.replace('meta', 'cmd'); |
|
143 | shortcut = shortcut.replace('meta', 'cmd'); | |
@@ -182,7 +178,7 b' IPython.keyboard = (function (IPython) {' | |||||
182 | this._shortcuts[shortcut] = data; |
|
178 | this._shortcuts[shortcut] = data; | |
183 | if (!suppress_help_update) { |
|
179 | if (!suppress_help_update) { | |
184 | // update the keyboard shortcuts notebook help |
|
180 | // update the keyboard shortcuts notebook help | |
185 |
|
|
181 | this.events.trigger('rebuild.QuickHelp'); | |
186 | } |
|
182 | } | |
187 | }; |
|
183 | }; | |
188 |
|
184 | |||
@@ -191,7 +187,7 b' IPython.keyboard = (function (IPython) {' | |||||
191 | this.add_shortcut(shortcut, data[shortcut], true); |
|
187 | this.add_shortcut(shortcut, data[shortcut], true); | |
192 | } |
|
188 | } | |
193 | // update the keyboard shortcuts notebook help |
|
189 | // update the keyboard shortcuts notebook help | |
194 |
|
|
190 | this.events.trigger('rebuild.QuickHelp'); | |
195 | }; |
|
191 | }; | |
196 |
|
192 | |||
197 | ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) { |
|
193 | ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) { | |
@@ -200,7 +196,7 b' IPython.keyboard = (function (IPython) {' | |||||
200 | delete this._shortcuts[shortcut]; |
|
196 | delete this._shortcuts[shortcut]; | |
201 | if (!suppress_help_update) { |
|
197 | if (!suppress_help_update) { | |
202 | // update the keyboard shortcuts notebook help |
|
198 | // update the keyboard shortcuts notebook help | |
203 |
|
|
199 | this.events.trigger('rebuild.QuickHelp'); | |
204 | } |
|
200 | } | |
205 | }; |
|
201 | }; | |
206 |
|
202 | |||
@@ -211,7 +207,7 b' IPython.keyboard = (function (IPython) {' | |||||
211 | var timer = null; |
|
207 | var timer = null; | |
212 | if (c[shortcut] === data.count-1) { |
|
208 | if (c[shortcut] === data.count-1) { | |
213 | c[shortcut] = 0; |
|
209 | c[shortcut] = 0; | |
214 |
|
|
210 | timer = t[shortcut]; | |
215 | if (timer) {clearTimeout(timer); delete t[shortcut];} |
|
211 | if (timer) {clearTimeout(timer); delete t[shortcut];} | |
216 | return data.handler(event); |
|
212 | return data.handler(event); | |
217 | } else { |
|
213 | } else { | |
@@ -228,7 +224,7 b' IPython.keyboard = (function (IPython) {' | |||||
228 | var shortcut = event_to_shortcut(event); |
|
224 | var shortcut = event_to_shortcut(event); | |
229 | var data = this._shortcuts[shortcut]; |
|
225 | var data = this._shortcuts[shortcut]; | |
230 | if (data) { |
|
226 | if (data) { | |
231 |
var handler = data |
|
227 | var handler = data.handler; | |
232 | if (handler) { |
|
228 | if (handler) { | |
233 | if (data.count === 1) { |
|
229 | if (data.count === 1) { | |
234 | return handler(event); |
|
230 | return handler(event); | |
@@ -243,10 +239,10 b' IPython.keyboard = (function (IPython) {' | |||||
243 | ShortcutManager.prototype.handles = function (event) { |
|
239 | ShortcutManager.prototype.handles = function (event) { | |
244 | var shortcut = event_to_shortcut(event); |
|
240 | var shortcut = event_to_shortcut(event); | |
245 | var data = this._shortcuts[shortcut]; |
|
241 | var data = this._shortcuts[shortcut]; | |
246 | return !( data === undefined || data.handler === undefined ) |
|
242 | return !( data === undefined || data.handler === undefined ); | |
247 | } |
|
243 | }; | |
248 |
|
244 | |||
249 | return { |
|
245 | var keyboard = { | |
250 | keycodes : keycodes, |
|
246 | keycodes : keycodes, | |
251 | inv_keycodes : inv_keycodes, |
|
247 | inv_keycodes : inv_keycodes, | |
252 | ShortcutManager : ShortcutManager, |
|
248 | ShortcutManager : ShortcutManager, | |
@@ -256,4 +252,8 b' IPython.keyboard = (function (IPython) {' | |||||
256 | event_to_shortcut : event_to_shortcut |
|
252 | event_to_shortcut : event_to_shortcut | |
257 | }; |
|
253 | }; | |
258 |
|
254 | |||
259 | }(IPython)); |
|
255 | // For backwards compatability. | |
|
256 | IPython.keyboard = keyboard; | |||
|
257 | ||||
|
258 | return keyboard; | |||
|
259 | }); |
@@ -1,34 +1,8 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | var IPython = IPython || {}; |
|
4 | var IPython = IPython || {}; | |
9 |
|
5 | define([], function(){ | ||
10 | IPython.version = "3.0.0-dev"; |
|
6 | IPython.version = "3.0.0-dev"; | |
11 |
|
7 | return IPython; | ||
12 | IPython.namespace = function (ns_string) { |
|
8 | }); | |
13 | "use strict"; |
|
|||
14 |
|
||||
15 | var parts = ns_string.split('.'), |
|
|||
16 | parent = IPython, |
|
|||
17 | i; |
|
|||
18 |
|
||||
19 | // String redundant leading global |
|
|||
20 | if (parts[0] === "IPython") { |
|
|||
21 | parts = parts.slice(1); |
|
|||
22 | } |
|
|||
23 |
|
||||
24 | for (i=0; i<parts.length; i+=1) { |
|
|||
25 | // Create property if it doesn't exist |
|
|||
26 | if (typeof parent[parts[i]] === "undefined") { |
|
|||
27 | parent[parts[i]] = {}; |
|
|||
28 | } |
|
|||
29 | } |
|
|||
30 | return parent; |
|
|||
31 | }; |
|
|||
32 |
|
||||
33 |
|
||||
34 |
|
@@ -1,32 +1,19 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2008-2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | define([ | |
9 | // Global header/site setup. |
|
5 | 'base/js/namespace', | |
10 | //============================================================================ |
|
6 | 'jquery', | |
11 |
|
7 | ], function(IPython, $){ | ||
12 | var IPython = (function (IPython) { |
|
|||
13 | "use strict"; |
|
8 | "use strict"; | |
14 |
|
9 | |||
15 | var Page = function () { |
|
10 | var Page = function () { | |
16 | this.style(); |
|
|||
17 | this.bind_events(); |
|
11 | this.bind_events(); | |
18 | }; |
|
12 | }; | |
19 |
|
13 | |||
20 | Page.prototype.style = function () { |
|
|||
21 | $('div#header').addClass('border-box-sizing'); |
|
|||
22 | $('div#site').addClass('border-box-sizing'); |
|
|||
23 | }; |
|
|||
24 |
|
||||
25 |
|
||||
26 | Page.prototype.bind_events = function () { |
|
14 | Page.prototype.bind_events = function () { | |
27 | }; |
|
15 | }; | |
28 |
|
16 | |||
29 |
|
||||
30 | Page.prototype.show = function () { |
|
17 | Page.prototype.show = function () { | |
31 | // The header and site divs start out hidden to prevent FLOUC. |
|
18 | // The header and site divs start out hidden to prevent FLOUC. | |
32 | // Main scripts should call this method after styling everything. |
|
19 | // Main scripts should call this method after styling everything. | |
@@ -34,23 +21,21 b' var IPython = (function (IPython) {' | |||||
34 | this.show_site(); |
|
21 | this.show_site(); | |
35 | }; |
|
22 | }; | |
36 |
|
23 | |||
37 |
|
||||
38 | Page.prototype.show_header = function () { |
|
24 | Page.prototype.show_header = function () { | |
39 | // The header and site divs start out hidden to prevent FLOUC. |
|
25 | // The header and site divs start out hidden to prevent FLOUC. | |
40 | // Main scripts should call this method after styling everything. |
|
26 | // Main scripts should call this method after styling everything. | |
|
27 | // TODO: selector are hardcoded, pass as constructor argument | |||
41 | $('div#header').css('display','block'); |
|
28 | $('div#header').css('display','block'); | |
42 | }; |
|
29 | }; | |
43 |
|
30 | |||
44 |
|
||||
45 | Page.prototype.show_site = function () { |
|
31 | Page.prototype.show_site = function () { | |
46 | // The header and site divs start out hidden to prevent FLOUC. |
|
32 | // The header and site divs start out hidden to prevent FLOUC. | |
47 | // Main scripts should call this method after styling everything. |
|
33 | // Main scripts should call this method after styling everything. | |
|
34 | // TODO: selector are hardcoded, pass as constructor argument | |||
48 | $('div#site').css('display','block'); |
|
35 | $('div#site').css('display','block'); | |
49 | }; |
|
36 | }; | |
50 |
|
37 | |||
51 |
|
38 | // Register self in the global namespace for convenience. | ||
52 | IPython.Page = Page; |
|
39 | IPython.Page = Page; | |
53 |
|
40 | return {'Page': Page}; | ||
54 | return IPython; |
|
41 | }); | |
55 |
|
||||
56 | }(IPython)); |
|
@@ -1,19 +1,12 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2014 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | define([ | |
9 | // Utilities |
|
5 | 'base/js/namespace', | |
10 | //============================================================================ |
|
6 | 'jquery', | |
11 | IPython.namespace('IPython.security'); |
|
7 | 'components/google-caja/html-css-sanitizer-minified', | |
12 |
|
8 | ], function(IPython, $) { | ||
13 | IPython.security = (function (IPython) { |
|
|||
14 | "use strict"; |
|
9 | "use strict"; | |
15 |
|
||||
16 | var utils = IPython.utils; |
|
|||
17 |
|
10 | |||
18 | var noop = function (x) { return x; }; |
|
11 | var noop = function (x) { return x; }; | |
19 |
|
12 | |||
@@ -117,10 +110,12 b' IPython.security = (function (IPython) {' | |||||
117 | return sanitized; |
|
110 | return sanitized; | |
118 | }; |
|
111 | }; | |
119 |
|
112 | |||
120 |
|
|
113 | var security = { | |
121 | caja: caja, |
|
114 | caja: caja, | |
122 | sanitize_html: sanitize_html |
|
115 | sanitize_html: sanitize_html | |
123 | }; |
|
116 | }; | |
124 |
|
117 | |||
125 | }(IPython)); |
|
118 | IPython.security = security; | |
126 |
|
119 | |||
|
120 | return security; | |||
|
121 | }); |
@@ -1,13 +1,10 b'' | |||||
1 | // Copyright (c) IPython Development Team. |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Distributed under the terms of the Modified BSD License. |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 |
|
3 | |||
4 | //============================================================================ |
|
4 | define([ | |
5 | // Utilities |
|
5 | 'base/js/namespace', | |
6 | //============================================================================ |
|
6 | 'jquery', | |
7 |
|
7 | ], function(IPython, $){ | ||
8 | IPython.namespace('IPython.utils'); |
|
|||
9 |
|
||||
10 | IPython.utils = (function (IPython) { |
|
|||
11 | "use strict"; |
|
8 | "use strict"; | |
12 |
|
9 | |||
13 | IPython.load_extensions = function () { |
|
10 | IPython.load_extensions = function () { | |
@@ -517,19 +514,24 b' IPython.utils = (function (IPython) {' | |||||
517 | } |
|
514 | } | |
518 | }; |
|
515 | }; | |
519 |
|
516 | |||
|
517 | var ajax_error_msg = function (jqXHR) { | |||
|
518 | // Return a JSON error message if there is one, | |||
|
519 | // otherwise the basic HTTP status text. | |||
|
520 | if (jqXHR.responseJSON && jqXHR.responseJSON.message) { | |||
|
521 | return jqXHR.responseJSON.message; | |||
|
522 | } else { | |||
|
523 | return jqXHR.statusText; | |||
|
524 | } | |||
|
525 | } | |||
520 | var log_ajax_error = function (jqXHR, status, error) { |
|
526 | var log_ajax_error = function (jqXHR, status, error) { | |
521 | // log ajax failures with informative messages |
|
527 | // log ajax failures with informative messages | |
522 | var msg = "API request failed (" + jqXHR.status + "): "; |
|
528 | var msg = "API request failed (" + jqXHR.status + "): "; | |
523 | console.log(jqXHR); |
|
529 | console.log(jqXHR); | |
524 | if (jqXHR.responseJSON && jqXHR.responseJSON.message) { |
|
530 | msg += ajax_error_msg(jqXHR); | |
525 | msg += jqXHR.responseJSON.message; |
|
|||
526 | } else { |
|
|||
527 | msg += jqXHR.statusText; |
|
|||
528 | } |
|
|||
529 | console.log(msg); |
|
531 | console.log(msg); | |
530 | }; |
|
532 | }; | |
531 |
|
533 | |||
532 | return { |
|
534 | var utils = { | |
533 | regex_split : regex_split, |
|
535 | regex_split : regex_split, | |
534 | uuid : uuid, |
|
536 | uuid : uuid, | |
535 | fixConsole : fixConsole, |
|
537 | fixConsole : fixConsole, | |
@@ -550,8 +552,12 b' IPython.utils = (function (IPython) {' | |||||
550 | platform: platform, |
|
552 | platform: platform, | |
551 | is_or_has : is_or_has, |
|
553 | is_or_has : is_or_has, | |
552 | is_focused : is_focused, |
|
554 | is_focused : is_focused, | |
|
555 | ajax_error_msg : ajax_error_msg, | |||
553 | log_ajax_error : log_ajax_error, |
|
556 | log_ajax_error : log_ajax_error, | |
554 | }; |
|
557 | }; | |
555 |
|
558 | |||
556 | }(IPython)); |
|
559 | // Backwards compatability. | |
557 |
|
560 | IPython.utils = utils; | ||
|
561 | ||||
|
562 | return utils; | |||
|
563 | }); |
@@ -184,6 +184,30 b' Browsers not listed, including Safari, are supported via the styling under the' | |||||
184 | justify-content: center; |
|
184 | justify-content: center; | |
185 | } |
|
185 | } | |
186 |
|
186 | |||
|
187 | .hbox.baseline, | |||
|
188 | .vbox.baseline, | |||
|
189 | .baseline { | |||
|
190 | /* Old browsers */ | |||
|
191 | -webkit-box-pack: baseline; | |||
|
192 | -moz-box-pack: baseline; | |||
|
193 | box-pack: baseline; | |||
|
194 | ||||
|
195 | /* Modern browsers */ | |||
|
196 | justify-content: baseline; | |||
|
197 | } | |||
|
198 | ||||
|
199 | .hbox.stretch, | |||
|
200 | .vbox.stretch, | |||
|
201 | .stretch { | |||
|
202 | /* Old browsers */ | |||
|
203 | -webkit-box-pack: stretch; | |||
|
204 | -moz-box-pack: stretch; | |||
|
205 | box-pack: stretch; | |||
|
206 | ||||
|
207 | /* Modern browsers */ | |||
|
208 | justify-content: stretch; | |||
|
209 | } | |||
|
210 | ||||
187 | .hbox.align-start, |
|
211 | .hbox.align-start, | |
188 | .vbox.align-start, |
|
212 | .vbox.align-start, | |
189 | .align-start { |
|
213 | .align-start { | |
@@ -219,3 +243,27 b' Browsers not listed, including Safari, are supported via the styling under the' | |||||
219 | /* Modern browsers */ |
|
243 | /* Modern browsers */ | |
220 | align-items: center; |
|
244 | align-items: center; | |
221 | } |
|
245 | } | |
|
246 | ||||
|
247 | .hbox.align-baseline, | |||
|
248 | .vbox.align-baseline, | |||
|
249 | .align-baseline { | |||
|
250 | /* Old browsers */ | |||
|
251 | -webkit-box-align: baseline; | |||
|
252 | -moz-box-align: baseline; | |||
|
253 | box-align: baseline; | |||
|
254 | ||||
|
255 | /* Modern browsers */ | |||
|
256 | align-items: baseline; | |||
|
257 | } | |||
|
258 | ||||
|
259 | .hbox.align-stretch, | |||
|
260 | .vbox.align-stretch, | |||
|
261 | .align-stretch { | |||
|
262 | /* Old browsers */ | |||
|
263 | -webkit-box-align: stretch; | |||
|
264 | -moz-box-align: stretch; | |||
|
265 | box-align: stretch; | |||
|
266 | ||||
|
267 | /* Modern browsers */ | |||
|
268 | align-items: stretch; | |||
|
269 | } |
@@ -24,6 +24,7 b' div#header {' | |||||
24 | padding-left: 30px; |
|
24 | padding-left: 30px; | |
25 | padding-bottom: 5px; |
|
25 | padding-bottom: 5px; | |
26 | border-bottom: 1px solid @navbar-default-border; |
|
26 | border-bottom: 1px solid @navbar-default-border; | |
|
27 | .border-box-sizing(); | |||
27 | } |
|
28 | } | |
28 |
|
29 | |||
29 | #ipython_notebook { |
|
30 | #ipython_notebook { | |
@@ -50,6 +51,7 b' div#header {' | |||||
50 | #site { |
|
51 | #site { | |
51 | width: 100%; |
|
52 | width: 100%; | |
52 | display: none; |
|
53 | display: none; | |
|
54 | .border-box-sizing(); | |||
53 | } |
|
55 | } | |
54 |
|
56 | |||
55 | /* Smaller buttons */ |
|
57 | /* Smaller buttons */ | |
@@ -69,6 +71,14 b' span#login_widget {' | |||||
69 | float: right; |
|
71 | float: right; | |
70 | } |
|
72 | } | |
71 |
|
73 | |||
|
74 | span#login_widget > .button, | |||
|
75 | #logout | |||
|
76 | { | |||
|
77 | .btn(); | |||
|
78 | .btn-default(); | |||
|
79 | .btn-sm(); | |||
|
80 | } | |||
|
81 | ||||
72 | .nav-header { |
|
82 | .nav-header { | |
73 | text-transform: none; |
|
83 | text-transform: none; | |
74 | } |
|
84 | } | |
@@ -93,3 +103,8 b' span#login_widget {' | |||||
93 | } |
|
103 | } | |
94 | } |
|
104 | } | |
95 |
|
105 | |||
|
106 | // less mixin to be sure to add the right class to get icons with font awesome. | |||
|
107 | .icon(@ico){ | |||
|
108 | .fa(); | |||
|
109 | content: @ico; | |||
|
110 | } |
@@ -1,3 +1,8 b'' | |||||
|
1 | /*! | |||
|
2 | * | |||
|
3 | * IPython base | |||
|
4 | * | |||
|
5 | */ | |||
1 | @import "variables.less"; |
|
6 | @import "variables.less"; | |
2 | @import "mixins.less"; |
|
7 | @import "mixins.less"; | |
3 | @import "flexbox.less"; |
|
8 | @import "flexbox.less"; |
@@ -1,1 +1,1 b'' | |||||
1 | Subproject commit 7c07f705d4f068828fb76f74ac7d4d355aea06f9 |
|
1 | Subproject commit b3909af1b61ca7a412481759fdb441ecdfb3ab66 |
@@ -12,12 +12,12 b'' | |||||
12 | * |
|
12 | * | |
13 | * Same thing with `profile/static/custom/custom.css` to inject custom css into the notebook. |
|
13 | * Same thing with `profile/static/custom/custom.css` to inject custom css into the notebook. | |
14 | * |
|
14 | * | |
15 | * Example : |
|
15 | * __Example 1:__ | |
16 | * |
|
16 | * | |
17 | * Create a custom button in toolbar that execute `%qtconsole` in kernel |
|
17 | * Create a custom button in toolbar that execute `%qtconsole` in kernel | |
18 | * and hence open a qtconsole attached to the same kernel as the current notebook |
|
18 | * and hence open a qtconsole attached to the same kernel as the current notebook | |
19 | * |
|
19 | * | |
20 |
* |
|
20 | * IPython.events.on('app_initialized.NotebookApp', function(){ | |
21 | * IPython.toolbar.add_buttons_group([ |
|
21 | * IPython.toolbar.add_buttons_group([ | |
22 | * { |
|
22 | * { | |
23 | * 'label' : 'run qtconsole', |
|
23 | * 'label' : 'run qtconsole', | |
@@ -30,7 +30,16 b'' | |||||
30 | * ]); |
|
30 | * ]); | |
31 | * }); |
|
31 | * }); | |
32 | * |
|
32 | * | |
33 | * Example : |
|
33 | * __Example 2:__ | |
|
34 | * | |||
|
35 | * At the completion of the dashboard loading, load an unofficial javascript extension | |||
|
36 | * that is installed in profile/static/custom/ | |||
|
37 | * | |||
|
38 | * IPython.events.on('app_initialized.DashboardApp', function(){ | |||
|
39 | * require(['custom/unofficial_extension.js']) | |||
|
40 | * }); | |||
|
41 | * | |||
|
42 | * __Example 3:__ | |||
34 | * |
|
43 | * | |
35 | * Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );` |
|
44 | * Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );` | |
36 | * to load custom script into the notebook. |
|
45 | * to load custom script into the notebook. |
@@ -122,4 +122,4 b' dateFormat.i18n = {' | |||||
122 | // For convenience... |
|
122 | // For convenience... | |
123 | Date.prototype.format = function (mask, utc) { |
|
123 | Date.prototype.format = function (mask, utc) { | |
124 | return dateFormat(this, mask, utc); |
|
124 | return dateFormat(this, mask, utc); | |
125 |
}; |
|
125 | }; No newline at end of file |
@@ -1,53 +1,61 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2008-2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
3 | ||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
4 | define([ | |
5 | // the file COPYING, distributed as part of this software. |
|
5 | 'base/js/namespace', | |
6 | //---------------------------------------------------------------------------- |
|
6 | 'jquery', | |
7 |
|
7 | 'base/js/utils', | ||
8 | //============================================================================ |
|
8 | ], function(IPython, $, utils) { | |
9 | // Cell |
|
9 | // TODO: remove IPython dependency here | |
10 | //============================================================================ |
|
|||
11 | /** |
|
|||
12 | * An extendable module that provide base functionnality to create cell for notebook. |
|
|||
13 | * @module IPython |
|
|||
14 | * @namespace IPython |
|
|||
15 | * @submodule Cell |
|
|||
16 | */ |
|
|||
17 |
|
||||
18 | var IPython = (function (IPython) { |
|
|||
19 | "use strict"; |
|
10 | "use strict"; | |
20 |
|
11 | |||
21 | var utils = IPython.utils; |
|
12 | // monkey patch CM to be able to syntax highlight cell magics | |
22 | var keycodes = IPython.keyboard.keycodes; |
|
13 | // bug reported upstream, | |
|
14 | // see https://github.com/marijnh/CodeMirror2/issues/670 | |||
|
15 | if(CodeMirror.getMode(1,'text/plain').indent === undefined ){ | |||
|
16 | CodeMirror.modes.null = function() { | |||
|
17 | return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}}; | |||
|
18 | }; | |||
|
19 | } | |||
23 |
|
20 | |||
24 | /** |
|
21 | CodeMirror.patchedGetMode = function(config, mode){ | |
25 | * The Base `Cell` class from which to inherit |
|
22 | var cmmode = CodeMirror.getMode(config, mode); | |
26 | * @class Cell |
|
23 | if(cmmode.indent === null) { | |
27 | **/ |
|
24 | console.log('patch mode "' , mode, '" on the fly'); | |
|
25 | cmmode.indent = function(){return 0;}; | |||
|
26 | } | |||
|
27 | return cmmode; | |||
|
28 | }; | |||
|
29 | // end monkey patching CodeMirror | |||
28 |
|
30 | |||
29 | /* |
|
|||
30 | * @constructor |
|
|||
31 | * |
|
|||
32 | * @param {object|undefined} [options] |
|
|||
33 | * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters |
|
|||
34 | */ |
|
|||
35 | var Cell = function (options) { |
|
31 | var Cell = function (options) { | |
36 |
|
32 | // Constructor | ||
37 | options = this.mergeopt(Cell, options); |
|
33 | // | |
|
34 | // The Base `Cell` class from which to inherit. | |||
|
35 | // | |||
|
36 | // Parameters: | |||
|
37 | // options: dictionary | |||
|
38 | // Dictionary of keyword arguments. | |||
|
39 | // events: $(Events) instance | |||
|
40 | // config: dictionary | |||
|
41 | // keyboard_manager: KeyboardManager instance | |||
|
42 | options = options || {}; | |||
|
43 | this.keyboard_manager = options.keyboard_manager; | |||
|
44 | this.events = options.events; | |||
|
45 | var config = this.mergeopt(Cell, options.config); | |||
38 | // superclass default overwrite our default |
|
46 | // superclass default overwrite our default | |
39 |
|
47 | |||
40 |
this.placeholder = |
|
48 | this.placeholder = config.placeholder || ''; | |
41 |
this.read_only = |
|
49 | this.read_only = config.cm_config.readOnly; | |
42 | this.selected = false; |
|
50 | this.selected = false; | |
43 | this.rendered = false; |
|
51 | this.rendered = false; | |
44 | this.mode = 'command'; |
|
52 | this.mode = 'command'; | |
45 | this.metadata = {}; |
|
53 | this.metadata = {}; | |
46 | // load this from metadata later ? |
|
54 | // load this from metadata later ? | |
47 | this.user_highlight = 'auto'; |
|
55 | this.user_highlight = 'auto'; | |
48 |
this.cm_config = |
|
56 | this.cm_config = config.cm_config; | |
49 | this.cell_id = utils.uuid(); |
|
57 | this.cell_id = utils.uuid(); | |
50 |
this._options = |
|
58 | this._options = config; | |
51 |
|
59 | |||
52 | // For JS VM engines optimization, attributes should be all set (even |
|
60 | // For JS VM engines optimization, attributes should be all set (even | |
53 | // to null) in the constructor, and if possible, if different subclass |
|
61 | // to null) in the constructor, and if possible, if different subclass | |
@@ -70,7 +78,12 b' var IPython = (function (IPython) {' | |||||
70 | cm_config : { |
|
78 | cm_config : { | |
71 | indentUnit : 4, |
|
79 | indentUnit : 4, | |
72 | readOnly: false, |
|
80 | readOnly: false, | |
73 | theme: "default" |
|
81 | theme: "default", | |
|
82 | extraKeys: { | |||
|
83 | "Cmd-Right":"goLineRight", | |||
|
84 | "End":"goLineRight", | |||
|
85 | "Cmd-Left":"goLineLeft" | |||
|
86 | } | |||
74 | } |
|
87 | } | |
75 | }; |
|
88 | }; | |
76 |
|
89 | |||
@@ -127,27 +140,27 b' var IPython = (function (IPython) {' | |||||
127 | // We trigger events so that Cell doesn't have to depend on Notebook. |
|
140 | // We trigger events so that Cell doesn't have to depend on Notebook. | |
128 | that.element.click(function (event) { |
|
141 | that.element.click(function (event) { | |
129 | if (!that.selected) { |
|
142 | if (!that.selected) { | |
130 |
|
|
143 | that.events.trigger('select.Cell', {'cell':that}); | |
131 | } |
|
144 | } | |
132 | }); |
|
145 | }); | |
133 | that.element.focusin(function (event) { |
|
146 | that.element.focusin(function (event) { | |
134 | if (!that.selected) { |
|
147 | if (!that.selected) { | |
135 |
|
|
148 | that.events.trigger('select.Cell', {'cell':that}); | |
136 | } |
|
149 | } | |
137 | }); |
|
150 | }); | |
138 | if (this.code_mirror) { |
|
151 | if (this.code_mirror) { | |
139 | this.code_mirror.on("change", function(cm, change) { |
|
152 | this.code_mirror.on("change", function(cm, change) { | |
140 |
|
|
153 | that.events.trigger("set_dirty.Notebook", {value: true}); | |
141 | }); |
|
154 | }); | |
142 | } |
|
155 | } | |
143 | if (this.code_mirror) { |
|
156 | if (this.code_mirror) { | |
144 | this.code_mirror.on('focus', function(cm, change) { |
|
157 | this.code_mirror.on('focus', function(cm, change) { | |
145 |
|
|
158 | that.events.trigger('edit_mode.Cell', {cell: that}); | |
146 | }); |
|
159 | }); | |
147 | } |
|
160 | } | |
148 | if (this.code_mirror) { |
|
161 | if (this.code_mirror) { | |
149 | this.code_mirror.on('blur', function(cm, change) { |
|
162 | this.code_mirror.on('blur', function(cm, change) { | |
150 |
|
|
163 | that.events.trigger('command_mode.Cell', {cell: that}); | |
151 | }); |
|
164 | }); | |
152 | } |
|
165 | } | |
153 | }; |
|
166 | }; | |
@@ -165,8 +178,7 b' var IPython = (function (IPython) {' | |||||
165 | * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise |
|
178 | * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise | |
166 | */ |
|
179 | */ | |
167 | Cell.prototype.handle_codemirror_keyevent = function (editor, event) { |
|
180 | Cell.prototype.handle_codemirror_keyevent = function (editor, event) { | |
168 | var that = this; |
|
181 | var shortcuts = this.keyboard_manager.edit_shortcuts; | |
169 | var shortcuts = IPython.keyboard_manager.edit_shortcuts; |
|
|||
170 |
|
182 | |||
171 | // if this is an edit_shortcuts shortcut, the global keyboard/shortcut |
|
183 | // if this is an edit_shortcuts shortcut, the global keyboard/shortcut | |
172 | // manager will handle it |
|
184 | // manager will handle it | |
@@ -544,9 +556,8 b' var IPython = (function (IPython) {' | |||||
544 | this.code_mirror.setOption('mode', default_mode); |
|
556 | this.code_mirror.setOption('mode', default_mode); | |
545 | }; |
|
557 | }; | |
546 |
|
558 | |||
|
559 | // Backwards compatibility. | |||
547 | IPython.Cell = Cell; |
|
560 | IPython.Cell = Cell; | |
548 |
|
561 | |||
549 | return IPython; |
|
562 | return {'Cell': Cell}; | |
550 |
|
563 | }); | ||
551 | }(IPython)); |
|
|||
552 |
|
@@ -1,32 +1,28 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2012 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
3 | ||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
4 | define([ | |
5 | // the file COPYING, distributed as part of this software. |
|
5 | 'base/js/namespace', | |
6 | //---------------------------------------------------------------------------- |
|
6 | 'jquery', | |
7 |
|
7 | 'base/js/events' | ||
8 | //============================================================================ |
|
8 | ], function(IPython, $, events) { | |
9 | // CellToolbar |
|
|||
10 | //============================================================================ |
|
|||
11 |
|
||||
12 |
|
||||
13 | /** |
|
|||
14 | * A Module to control the per-cell toolbar. |
|
|||
15 | * @module IPython |
|
|||
16 | * @namespace IPython |
|
|||
17 | * @submodule CellToolbar |
|
|||
18 | */ |
|
|||
19 | var IPython = (function (IPython) { |
|
|||
20 | "use strict"; |
|
9 | "use strict"; | |
21 |
|
10 | |||
22 | /** |
|
11 | var CellToolbar = function (options) { | |
23 |
|
|
12 | // Constructor | |
24 | * @class CellToolbar |
|
13 | // | |
25 | * @param {The cell to attach the metadata UI to} cell |
|
14 | // Parameters: | |
26 | */ |
|
15 | // options: dictionary | |
27 | var CellToolbar = function (cell) { |
|
16 | // Dictionary of keyword arguments. | |
|
17 | // events: $(Events) instance | |||
|
18 | // cell: Cell instance | |||
|
19 | // notebook: Notebook instance | |||
|
20 | // | |||
|
21 | // TODO: This leaks, when cell are deleted | |||
|
22 | // There is still a reference to each celltoolbars. | |||
28 | CellToolbar._instances.push(this); |
|
23 | CellToolbar._instances.push(this); | |
29 | this.cell = cell; |
|
24 | this.notebook = options.notebook; | |
|
25 | this.cell = options.cell; | |||
30 | this.create_element(); |
|
26 | this.create_element(); | |
31 | this.rebuild(); |
|
27 | this.rebuild(); | |
32 | return this; |
|
28 | return this; | |
@@ -34,7 +30,7 b' var IPython = (function (IPython) {' | |||||
34 |
|
30 | |||
35 |
|
31 | |||
36 | CellToolbar.prototype.create_element = function () { |
|
32 | CellToolbar.prototype.create_element = function () { | |
37 | this.inner_element = $('<div/>').addClass('celltoolbar') |
|
33 | this.inner_element = $('<div/>').addClass('celltoolbar'); | |
38 | this.element = $('<div/>').addClass('ctb_hideshow') |
|
34 | this.element = $('<div/>').addClass('ctb_hideshow') | |
39 | .append(this.inner_element); |
|
35 | .append(this.inner_element); | |
40 | }; |
|
36 | }; | |
@@ -182,13 +178,14 b' var IPython = (function (IPython) {' | |||||
182 | * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5']) |
|
178 | * CellToolbar.register_preset('foo.foo_preset1', ['foo.c1', 'foo.c2', 'foo.c5']) | |
183 | * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5']) |
|
179 | * CellToolbar.register_preset('foo.foo_preset2', ['foo.c4', 'foo.c5']) | |
184 | */ |
|
180 | */ | |
185 | CellToolbar.register_preset = function(name, preset_list) { |
|
181 | CellToolbar.register_preset = function(name, preset_list, notebook) { | |
186 | CellToolbar._presets[name] = preset_list; |
|
182 | CellToolbar._presets[name] = preset_list; | |
187 |
|
|
183 | events.trigger('preset_added.CellToolbar', {name: name}); | |
188 | // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded. |
|
184 | // When "register_callback" is called by a custom extension, it may be executed after notebook is loaded. | |
189 | // In that case, activate the preset if needed. |
|
185 | // In that case, activate the preset if needed. | |
190 |
if ( |
|
186 | if (notebook && notebook.metadata && notebook.metadata.celltoolbar === name){ | |
191 |
|
|
187 | CellToolbar.activate_preset(name); | |
|
188 | } | |||
192 | }; |
|
189 | }; | |
193 |
|
190 | |||
194 |
|
191 | |||
@@ -229,7 +226,7 b' var IPython = (function (IPython) {' | |||||
229 | CellToolbar.rebuild_all(); |
|
226 | CellToolbar.rebuild_all(); | |
230 | } |
|
227 | } | |
231 |
|
228 | |||
232 |
|
|
229 | events.trigger('preset_activated.CellToolbar', {name: preset_name}); | |
233 | }; |
|
230 | }; | |
234 |
|
231 | |||
235 |
|
232 | |||
@@ -283,7 +280,7 b' var IPython = (function (IPython) {' | |||||
283 | } |
|
280 | } | |
284 |
|
281 | |||
285 | // If there are no controls or the cell is a rendered TextCell hide the toolbar. |
|
282 | // If there are no controls or the cell is a rendered TextCell hide the toolbar. | |
286 |
if (!this.ui_controls_list.length || (this.cell |
|
283 | if (!this.ui_controls_list.length || (this.cell.cell_type != 'code' && this.cell.rendered)) { | |
287 | this.hide(); |
|
284 | this.hide(); | |
288 | } else { |
|
285 | } else { | |
289 | this.show(); |
|
286 | this.show(); | |
@@ -347,7 +344,7 b' var IPython = (function (IPython) {' | |||||
347 | setter(cell, !v); |
|
344 | setter(cell, !v); | |
348 | chkb.attr("checked", !v); |
|
345 | chkb.attr("checked", !v); | |
349 | }); |
|
346 | }); | |
350 |
button_container.append($('< |
|
347 | button_container.append($('<span/>').append(lbl)); | |
351 | }; |
|
348 | }; | |
352 | }; |
|
349 | }; | |
353 |
|
350 | |||
@@ -411,12 +408,12 b' var IPython = (function (IPython) {' | |||||
411 | select.change(function(){ |
|
408 | select.change(function(){ | |
412 | setter(cell, select.val()); |
|
409 | setter(cell, select.val()); | |
413 | }); |
|
410 | }); | |
414 |
button_container.append($('< |
|
411 | button_container.append($('<span/>').append(lbl).append(select)); | |
415 | }; |
|
412 | }; | |
416 | }; |
|
413 | }; | |
417 |
|
414 | |||
418 |
|
415 | // Backwards compatability. | ||
419 | IPython.CellToolbar = CellToolbar; |
|
416 | IPython.CellToolbar = CellToolbar; | |
420 |
|
417 | |||
421 | return IPython; |
|
418 | return {'CellToolbar': CellToolbar}; | |
422 | }(IPython)); |
|
419 | }); |
@@ -1,31 +1,29 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2012 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
3 | ||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
4 | define([ | |
5 | // the file COPYING, distributed as part of this software. |
|
5 | 'jquery', | |
6 | //---------------------------------------------------------------------------- |
|
6 | 'notebook/js/celltoolbar', | |
7 |
|
7 | 'base/js/dialog', | ||
8 | //============================================================================ |
|
8 | ], function($, celltoolbar, dialog) { | |
9 | // CellToolbar Default |
|
|||
10 | //============================================================================ |
|
|||
11 |
|
||||
12 | /** |
|
|||
13 | * Example Use for the CellToolbar library |
|
|||
14 | */ |
|
|||
15 | // IIFE without asignement, we don't modifiy the IPython namespace |
|
|||
16 | (function (IPython) { |
|
|||
17 | "use strict"; |
|
9 | "use strict"; | |
18 |
|
10 | |||
19 |
var CellToolbar = |
|
11 | var CellToolbar = celltoolbar.CellToolbar; | |
20 |
|
12 | |||
21 | var raw_edit = function(cell){ |
|
13 | var raw_edit = function (cell) { | |
22 |
|
|
14 | dialog.edit_metadata({ | |
23 |
cell.metadata |
|
15 | md: cell.metadata, | |
|
16 | callback: function (md) { | |||
|
17 | cell.metadata = md; | |||
|
18 | }, | |||
|
19 | name: 'Cell', | |||
|
20 | notebook: this.notebook, | |||
|
21 | keyboard_manager: this.keyboard_manager | |||
24 | }); |
|
22 | }); | |
25 | }; |
|
23 | }; | |
26 |
|
24 | |||
27 | var add_raw_edit_button = function(div, cell) { |
|
25 | var add_raw_edit_button = function(div, cell) { | |
28 | var button_container = div; |
|
26 | var button_container = $(div); | |
29 | var button = $('<button/>') |
|
27 | var button = $('<button/>') | |
30 | .addClass("btn btn-default btn-xs") |
|
28 | .addClass("btn btn-default btn-xs") | |
31 | .text("Edit Metadata") |
|
29 | .text("Edit Metadata") | |
@@ -36,11 +34,18 b'' | |||||
36 | button_container.append(button); |
|
34 | button_container.append(button); | |
37 | }; |
|
35 | }; | |
38 |
|
36 | |||
39 | CellToolbar.register_callback('default.rawedit', add_raw_edit_button); |
|
37 | var register = function (notebook) { | |
40 | var example_preset = []; |
|
38 | CellToolbar.register_callback('default.rawedit', add_raw_edit_button); | |
41 | example_preset.push('default.rawedit'); |
|
39 | raw_edit = $.proxy(raw_edit, { | |
|
40 | notebook: notebook, | |||
|
41 | keyboard_manager: notebook.keyboard_manager | |||
|
42 | }); | |||
42 |
|
43 | |||
43 | CellToolbar.register_preset('Edit Metadata', example_preset); |
|
44 | var example_preset = []; | |
44 | console.log('Default extension for cell metadata editing loaded.'); |
|
45 | example_preset.push('default.rawedit'); | |
45 |
|
46 | |||
46 | }(IPython)); |
|
47 | CellToolbar.register_preset('Edit Metadata', example_preset, notebook); | |
|
48 | console.log('Default extension for cell metadata editing loaded.'); | |||
|
49 | }; | |||
|
50 | return {'register': register}; | |||
|
51 | }); |
@@ -1,28 +1,20 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2012 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
3 | ||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
4 | // Example Use for the CellToolbar library | |
5 | // the file COPYING, distributed as part of this software. |
|
5 | // add the following to your custom.js to load | |
6 | //---------------------------------------------------------------------------- |
|
6 | // Celltoolbar UI for slideshow | |
7 |
|
7 | |||
8 | //============================================================================ |
|
8 | // ``` | |
9 | // CellToolbar Example |
|
9 | // $.getScript('/static/js/celltoolbarpresets/example.js'); | |
10 | //============================================================================ |
|
10 | // ``` | |
11 |
|
11 | define([ | ||
12 | /** |
|
12 | 'jquery', | |
13 | * Example Use for the CellToolbar library |
|
13 | 'notebook/js/celltoolbar', | |
14 | * add the following to your custom.js to load |
|
14 | ], function($, celltoolbar) { | |
15 | * Celltoolbar UI for slideshow |
|
|||
16 | * |
|
|||
17 | * ``` |
|
|||
18 | * $.getScript('/static/js/celltoolbarpresets/example.js'); |
|
|||
19 | * ``` |
|
|||
20 | */ |
|
|||
21 | // IIFE without asignement, we don't modifiy the IPython namespace |
|
|||
22 | (function (IPython) { |
|
|||
23 | "use strict"; |
|
15 | "use strict"; | |
24 |
|
16 | |||
25 |
var CellToolbar = |
|
17 | var CellToolbar = celltoolbar.CellToolbar; | |
26 |
|
18 | |||
27 | var example_preset = []; |
|
19 | var example_preset = []; | |
28 |
|
20 | |||
@@ -32,32 +24,32 b'' | |||||
32 | var fun = function(value){ |
|
24 | var fun = function(value){ | |
33 | try{ |
|
25 | try{ | |
34 | if(value){ |
|
26 | if(value){ | |
35 | cell.code_mirror.setOption('readOnly','nocursor') |
|
27 | cell.code_mirror.setOption('readOnly','nocursor'); | |
36 | button.button('option','icons',{primary:'ui-icon-locked'}) |
|
28 | button.button('option','icons',{primary:'ui-icon-locked'}); | |
37 | } else { |
|
29 | } else { | |
38 | cell.code_mirror.setOption('readOnly',false) |
|
30 | cell.code_mirror.setOption('readOnly',false); | |
39 | button.button('option','icons',{primary:'ui-icon-unlocked'}) |
|
31 | button.button('option','icons',{primary:'ui-icon-unlocked'}); | |
40 | } |
|
32 | } | |
41 | } catch(e){} |
|
33 | } catch(e){} | |
42 |
|
34 | |||
43 | } |
|
35 | }; | |
44 | fun(cell.metadata.ro) |
|
36 | fun(cell.metadata.ro); | |
45 | button.click(function(){ |
|
37 | button.click(function(){ | |
46 | var v = cell.metadata.ro; |
|
38 | var v = cell.metadata.ro; | |
47 | var locked = !v; |
|
39 | var locked = !v; | |
48 | cell.metadata.ro = locked; |
|
40 | cell.metadata.ro = locked; | |
49 | fun(locked) |
|
41 | fun(locked); | |
50 | }) |
|
42 | }) | |
51 | .css('height','16px') |
|
43 | .css('height','16px') | |
52 | .css('width','35px'); |
|
44 | .css('width','35px'); | |
53 | button_container.append(button); |
|
45 | button_container.append(button); | |
54 | } |
|
46 | }; | |
55 |
|
47 | |||
56 | CellToolbar.register_callback('example.lock',simple_button); |
|
48 | CellToolbar.register_callback('example.lock',simple_button); | |
57 | example_preset.push('example.lock'); |
|
49 | example_preset.push('example.lock'); | |
58 |
|
50 | |||
59 | var toggle_test = function(div, cell) { |
|
51 | var toggle_test = function(div, cell) { | |
60 | var button_container = $(div) |
|
52 | var button_container = $(div); | |
61 | var button = $('<div/>') |
|
53 | var button = $('<div/>') | |
62 | .button({label:String(cell.metadata.foo)}). |
|
54 | .button({label:String(cell.metadata.foo)}). | |
63 | css('width','65px'); |
|
55 | css('width','65px'); | |
@@ -65,9 +57,9 b'' | |||||
65 | var v = cell.metadata.foo; |
|
57 | var v = cell.metadata.foo; | |
66 | cell.metadata.foo = !v; |
|
58 | cell.metadata.foo = !v; | |
67 | button.button("option","label",String(!v)); |
|
59 | button.button("option","label",String(!v)); | |
68 | }) |
|
60 | }); | |
69 | button_container.append(button); |
|
61 | button_container.append(button); | |
70 | } |
|
62 | }; | |
71 |
|
63 | |||
72 | CellToolbar.register_callback('example.toggle',toggle_test); |
|
64 | CellToolbar.register_callback('example.toggle',toggle_test); | |
73 | example_preset.push('example.toggle'); |
|
65 | example_preset.push('example.toggle'); | |
@@ -76,16 +68,16 b'' | |||||
76 | // setter |
|
68 | // setter | |
77 | function(cell, value){ |
|
69 | function(cell, value){ | |
78 | // we check that the slideshow namespace exist and create it if needed |
|
70 | // we check that the slideshow namespace exist and create it if needed | |
79 | if (cell.metadata.yn_test == undefined){cell.metadata.yn_test = {}} |
|
71 | if (cell.metadata.yn_test === undefined){cell.metadata.yn_test = {};} | |
80 | // set the value |
|
72 | // set the value | |
81 | cell.metadata.yn_test.value = value |
|
73 | cell.metadata.yn_test.value = value; | |
82 | }, |
|
74 | }, | |
83 | //geter |
|
75 | //geter | |
84 | function(cell){ var ns = cell.metadata.yn_test; |
|
76 | function(cell){ var ns = cell.metadata.yn_test; | |
85 | // if the slideshow namespace does not exist return `undefined` |
|
77 | // if the slideshow namespace does not exist return `undefined` | |
86 | // (will be interpreted as `false` by checkbox) otherwise |
|
78 | // (will be interpreted as `false` by checkbox) otherwise | |
87 | // return the value |
|
79 | // return the value | |
88 | return (ns == undefined)? undefined: ns.value |
|
80 | return (ns === undefined)? undefined: ns.value; | |
89 | } |
|
81 | } | |
90 | ); |
|
82 | ); | |
91 |
|
83 | |||
@@ -103,16 +95,16 b'' | |||||
103 | // setter |
|
95 | // setter | |
104 | function(cell,value){ |
|
96 | function(cell,value){ | |
105 | // we check that the slideshow namespace exist and create it if needed |
|
97 | // we check that the slideshow namespace exist and create it if needed | |
106 | if (cell.metadata.test == undefined){cell.metadata.test = {}} |
|
98 | if (cell.metadata.test === undefined){cell.metadata.test = {};} | |
107 | // set the value |
|
99 | // set the value | |
108 | cell.metadata.test.slide_type = value |
|
100 | cell.metadata.test.slide_type = value; | |
109 | }, |
|
101 | }, | |
110 | //geter |
|
102 | //geter | |
111 | function(cell){ var ns = cell.metadata.test; |
|
103 | function(cell){ var ns = cell.metadata.test; | |
112 | // if the slideshow namespace does not exist return `undefined` |
|
104 | // if the slideshow namespace does not exist return `undefined` | |
113 | // (will be interpreted as `false` by checkbox) otherwise |
|
105 | // (will be interpreted as `false` by checkbox) otherwise | |
114 | // return the value |
|
106 | // return the value | |
115 | return (ns == undefined)? undefined: ns.slide_type |
|
107 | return (ns === undefined)? undefined: ns.slide_type; | |
116 | }); |
|
108 | }); | |
117 |
|
109 | |||
118 | CellToolbar.register_callback('example.select',select_test); |
|
110 | CellToolbar.register_callback('example.select',select_test); | |
@@ -120,7 +112,7 b'' | |||||
120 |
|
112 | |||
121 | var simple_dialog = function(title,text){ |
|
113 | var simple_dialog = function(title,text){ | |
122 | var dlg = $('<div/>').attr('title',title) |
|
114 | var dlg = $('<div/>').attr('title',title) | |
123 | .append($('<p/>').text(text)) |
|
115 | .append($('<p/>').text(text)); | |
124 | $(dlg).dialog({ |
|
116 | $(dlg).dialog({ | |
125 | autoOpen: true, |
|
117 | autoOpen: true, | |
126 | height: 300, |
|
118 | height: 300, | |
@@ -131,24 +123,26 b'' | |||||
131 | $(this).remove(); |
|
123 | $(this).remove(); | |
132 | } |
|
124 | } | |
133 | }); |
|
125 | }); | |
134 | } |
|
126 | }; | |
135 |
|
127 | |||
136 | var add_simple_dialog_button = function(div, cell) { |
|
128 | var add_simple_dialog_button = function(div, cell) { | |
137 | var help_text = ["This is the Metadata editting UI.", |
|
129 | var help_text = ["This is the Metadata editting UI.", | |
138 | "It heavily rely on plugin to work ", |
|
130 | "It heavily rely on plugin to work ", | |
139 | "and is still under developpement. You shouldn't wait too long before", |
|
131 | "and is still under developpement. You shouldn't wait too long before", | |
140 | " seeing some customisable buttons in those toolbar." |
|
132 | " seeing some customisable buttons in those toolbar." | |
141 | ].join('\n') |
|
133 | ].join('\n'); | |
142 | var button_container = $(div) |
|
134 | var button_container = $(div); | |
143 | var button = $('<div/>').button({label:'?'}) |
|
135 | var button = $('<div/>').button({label:'?'}) | |
144 | .click(function(){simple_dialog('help',help_text); return false;}) |
|
136 | .click(function(){simple_dialog('help',help_text); return false;}); | |
145 | button_container.append(button); |
|
137 | button_container.append(button); | |
146 | } |
|
138 | }; | |
147 |
|
||||
148 | CellToolbar.register_callback('example.help',add_simple_dialog_button) |
|
|||
149 | example_preset.push('example.help') |
|
|||
150 |
|
139 | |||
151 | CellToolbar.register_preset('Example',example_preset); |
|
140 | var register = function (notebook) { | |
152 | console.log('Example extension for metadata editing loaded.'); |
|
141 | CellToolbar.register_callback('example.help',add_simple_dialog_button); | |
|
142 | example_preset.push('example.help'); | |||
153 |
|
143 | |||
154 | }(IPython)); |
|
144 | CellToolbar.register_preset('Example',example_preset, notebook); | |
|
145 | console.log('Example extension for metadata editing loaded.'); | |||
|
146 | }; | |||
|
147 | return {'register': register}; | |||
|
148 | }); |
@@ -1,18 +1,15 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2012 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | define([ | |
9 | // CellToolbar Example |
|
5 | 'jquery', | |
10 | //============================================================================ |
|
6 | 'notebook/js/celltoolbar', | |
|
7 | 'base/js/dialog', | |||
|
8 | 'base/js/keyboard', | |||
|
9 | ], function($, celltoolbar, dialog, keyboard) { | |||
|
10 | "use strict"; | |||
11 |
|
11 | |||
12 | (function(IPython) { |
|
12 | var CellToolbar = celltoolbar.CellToolbar; | |
13 | "use strict"; |
|
|||
14 |
|
||||
15 | var CellToolbar = IPython.CellToolbar; |
|
|||
16 | var raw_cell_preset = []; |
|
13 | var raw_cell_preset = []; | |
17 |
|
14 | |||
18 | var select_type = CellToolbar.utils.select_ui_generator([ |
|
15 | var select_type = CellToolbar.utils.select_ui_generator([ | |
@@ -39,7 +36,7 b'' | |||||
39 | $('<input/>').attr('type','text').attr('size','25') |
|
36 | $('<input/>').attr('type','text').attr('size','25') | |
40 | .val(cell.metadata.raw_mimetype || "-") |
|
37 | .val(cell.metadata.raw_mimetype || "-") | |
41 | ); |
|
38 | ); | |
42 |
|
|
39 | dialog.modal({ | |
43 | title: "Raw Cell MIME Type", |
|
40 | title: "Raw Cell MIME Type", | |
44 | body: dialog, |
|
41 | body: dialog, | |
45 | buttons : { |
|
42 | buttons : { | |
@@ -57,7 +54,7 b'' | |||||
57 | var that = $(this); |
|
54 | var that = $(this); | |
58 | // Upon ENTER, click the OK button. |
|
55 | // Upon ENTER, click the OK button. | |
59 | that.find('input[type="text"]').keydown(function (event, ui) { |
|
56 | that.find('input[type="text"]').keydown(function (event, ui) { | |
60 |
if (event.which === |
|
57 | if (event.which === keyboard.keycodes.enter) { | |
61 | that.find('.btn-primary').first().click(); |
|
58 | that.find('.btn-primary').first().click(); | |
62 | return false; |
|
59 | return false; | |
63 | } |
|
60 | } | |
@@ -77,11 +74,13 b'' | |||||
77 | "Raw NBConvert Format" |
|
74 | "Raw NBConvert Format" | |
78 | ); |
|
75 | ); | |
79 |
|
76 | |||
80 | CellToolbar.register_callback('raw_cell.select', select_type, ['raw']); |
|
77 | var register = function (notebook) { | |
81 |
|
78 | CellToolbar.register_callback('raw_cell.select', select_type, ['raw']); | ||
82 | raw_cell_preset.push('raw_cell.select'); |
|
79 | raw_cell_preset.push('raw_cell.select'); | |
83 |
|
80 | |||
84 | CellToolbar.register_preset('Raw Cell Format', raw_cell_preset); |
|
81 | CellToolbar.register_preset('Raw Cell Format', raw_cell_preset, notebook); | |
85 | console.log('Raw Cell Format toolbar preset loaded.'); |
|
82 | console.log('Raw Cell Format toolbar preset loaded.'); | |
|
83 | }; | |||
|
84 | return {'register': register}; | |||
86 |
|
85 | |||
87 | }(IPython)); |
|
86 | }); |
@@ -1,19 +1,14 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2012 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | define([ | |
9 | //CellToolbar Example |
|
5 | 'jquery', | |
10 | //============================================================================ |
|
6 | 'notebook/js/celltoolbar', | |
11 |
|
7 | ], function($, celltoolbar) { | ||
12 | // IIFE without asignement, we don't modifiy the IPython namespace |
|
|||
13 | (function (IPython) { |
|
|||
14 | "use strict"; |
|
8 | "use strict"; | |
15 |
|
9 | |||
16 | var CellToolbar = IPython.CellToolbar; |
|
10 | ||
|
11 | var CellToolbar = celltoolbar.CellToolbar; | |||
17 | var slideshow_preset = []; |
|
12 | var slideshow_preset = []; | |
18 |
|
13 | |||
19 | var select_type = CellToolbar.utils.select_ui_generator([ |
|
14 | var select_type = CellToolbar.utils.select_ui_generator([ | |
@@ -27,24 +22,25 b'' | |||||
27 | // setter |
|
22 | // setter | |
28 | function(cell, value){ |
|
23 | function(cell, value){ | |
29 | // we check that the slideshow namespace exist and create it if needed |
|
24 | // we check that the slideshow namespace exist and create it if needed | |
30 | if (cell.metadata.slideshow == undefined){cell.metadata.slideshow = {}} |
|
25 | if (cell.metadata.slideshow === undefined){cell.metadata.slideshow = {};} | |
31 | // set the value |
|
26 | // set the value | |
32 | cell.metadata.slideshow.slide_type = value |
|
27 | cell.metadata.slideshow.slide_type = value; | |
33 | }, |
|
28 | }, | |
34 | //geter |
|
29 | //geter | |
35 | function(cell){ var ns = cell.metadata.slideshow; |
|
30 | function(cell){ var ns = cell.metadata.slideshow; | |
36 | // if the slideshow namespace does not exist return `undefined` |
|
31 | // if the slideshow namespace does not exist return `undefined` | |
37 | // (will be interpreted as `false` by checkbox) otherwise |
|
32 | // (will be interpreted as `false` by checkbox) otherwise | |
38 | // return the value |
|
33 | // return the value | |
39 | return (ns == undefined)? undefined: ns.slide_type |
|
34 | return (ns === undefined)? undefined: ns.slide_type; | |
40 | }, |
|
35 | }, | |
41 | "Slide Type"); |
|
36 | "Slide Type"); | |
42 |
|
37 | |||
43 | CellToolbar.register_callback('slideshow.select',select_type); |
|
38 | var register = function (notebook) { | |
44 |
|
39 | CellToolbar.register_callback('slideshow.select',select_type); | ||
45 | slideshow_preset.push('slideshow.select'); |
|
40 | slideshow_preset.push('slideshow.select'); | |
46 |
|
||||
47 | CellToolbar.register_preset('Slideshow',slideshow_preset); |
|
|||
48 | console.log('Slideshow extension for metadata editing loaded.'); |
|
|||
49 |
|
41 | |||
50 | }(IPython)); |
|
42 | CellToolbar.register_preset('Slideshow',slideshow_preset, notebook); | |
|
43 | console.log('Slideshow extension for metadata editing loaded.'); | |||
|
44 | }; | |||
|
45 | return {'register': register}; | |||
|
46 | }); |
@@ -1,68 +1,67 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2008-2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
3 | ||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
4 | define([ | |
5 | // the file COPYING, distributed as part of this software. |
|
5 | 'base/js/namespace', | |
6 | //---------------------------------------------------------------------------- |
|
6 | 'jquery', | |
7 |
|
7 | 'base/js/utils', | ||
8 | //============================================================================ |
|
8 | 'base/js/keyboard', | |
9 | // CodeCell |
|
9 | 'notebook/js/cell', | |
10 | //============================================================================ |
|
10 | 'notebook/js/outputarea', | |
11 | /** |
|
11 | 'notebook/js/completer', | |
12 | * An extendable module that provide base functionnality to create cell for notebook. |
|
12 | 'notebook/js/celltoolbar', | |
13 | * @module IPython |
|
13 | ], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) { | |
14 | * @namespace IPython |
|
|||
15 | * @submodule CodeCell |
|
|||
16 | */ |
|
|||
17 |
|
||||
18 |
|
||||
19 | /* local util for codemirror */ |
|
|||
20 | var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}; |
|
|||
21 |
|
||||
22 | /** |
|
|||
23 | * |
|
|||
24 | * function to delete until previous non blanking space character |
|
|||
25 | * or first multiple of 4 tabstop. |
|
|||
26 | * @private |
|
|||
27 | */ |
|
|||
28 | CodeMirror.commands.delSpaceToPrevTabStop = function(cm){ |
|
|||
29 | var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); |
|
|||
30 | if (!posEq(from, to)) { cm.replaceRange("", from, to); return; } |
|
|||
31 | var cur = cm.getCursor(), line = cm.getLine(cur.line); |
|
|||
32 | var tabsize = cm.getOption('tabSize'); |
|
|||
33 | var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize; |
|
|||
34 | from = {ch:cur.ch-chToPrevTabStop,line:cur.line}; |
|
|||
35 | var select = cm.getRange(from,cur); |
|
|||
36 | if( select.match(/^\ +$/) !== null){ |
|
|||
37 | cm.replaceRange("",from,cur); |
|
|||
38 | } else { |
|
|||
39 | cm.deleteH(-1,"char"); |
|
|||
40 | } |
|
|||
41 | }; |
|
|||
42 |
|
||||
43 |
|
||||
44 | var IPython = (function (IPython) { |
|
|||
45 | "use strict"; |
|
14 | "use strict"; | |
|
15 | var Cell = cell.Cell; | |||
46 |
|
16 | |||
47 | var utils = IPython.utils; |
|
17 | /* local util for codemirror */ | |
48 | var keycodes = IPython.keyboard.keycodes; |
|
18 | var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}; | |
49 |
|
19 | |||
50 | /** |
|
20 | /** | |
51 | * A Cell conceived to write code. |
|
|||
52 | * |
|
21 | * | |
53 | * The kernel doesn't have to be set at creation time, in that case |
|
22 | * function to delete until previous non blanking space character | |
54 | * it will be null and set_kernel has to be called later. |
|
23 | * or first multiple of 4 tabstop. | |
55 | * @class CodeCell |
|
24 | * @private | |
56 | * @extends IPython.Cell |
|
|||
57 | * |
|
|||
58 | * @constructor |
|
|||
59 | * @param {Object|null} kernel |
|
|||
60 | * @param {object|undefined} [options] |
|
|||
61 | * @param [options.cm_config] {object} config to pass to CodeMirror |
|
|||
62 | */ |
|
25 | */ | |
|
26 | CodeMirror.commands.delSpaceToPrevTabStop = function(cm){ | |||
|
27 | var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); | |||
|
28 | if (!posEq(from, to)) { cm.replaceRange("", from, to); return; } | |||
|
29 | var cur = cm.getCursor(), line = cm.getLine(cur.line); | |||
|
30 | var tabsize = cm.getOption('tabSize'); | |||
|
31 | var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize; | |||
|
32 | from = {ch:cur.ch-chToPrevTabStop,line:cur.line}; | |||
|
33 | var select = cm.getRange(from,cur); | |||
|
34 | if( select.match(/^\ +$/) !== null){ | |||
|
35 | cm.replaceRange("",from,cur); | |||
|
36 | } else { | |||
|
37 | cm.deleteH(-1,"char"); | |||
|
38 | } | |||
|
39 | }; | |||
|
40 | ||||
|
41 | var keycodes = keyboard.keycodes; | |||
|
42 | ||||
63 | var CodeCell = function (kernel, options) { |
|
43 | var CodeCell = function (kernel, options) { | |
|
44 | // Constructor | |||
|
45 | // | |||
|
46 | // A Cell conceived to write code. | |||
|
47 | // | |||
|
48 | // Parameters: | |||
|
49 | // kernel: Kernel instance | |||
|
50 | // The kernel doesn't have to be set at creation time, in that case | |||
|
51 | // it will be null and set_kernel has to be called later. | |||
|
52 | // options: dictionary | |||
|
53 | // Dictionary of keyword arguments. | |||
|
54 | // events: $(Events) instance | |||
|
55 | // config: dictionary | |||
|
56 | // keyboard_manager: KeyboardManager instance | |||
|
57 | // notebook: Notebook instance | |||
|
58 | // tooltip: Tooltip instance | |||
64 | this.kernel = kernel || null; |
|
59 | this.kernel = kernel || null; | |
|
60 | this.notebook = options.notebook; | |||
65 | this.collapsed = false; |
|
61 | this.collapsed = false; | |
|
62 | this.events = options.events; | |||
|
63 | this.tooltip = options.tooltip; | |||
|
64 | this.config = options.config; | |||
66 |
|
65 | |||
67 | // create all attributed in constructor function |
|
66 | // create all attributed in constructor function | |
68 | // even if null for V8 VM optimisation |
|
67 | // even if null for V8 VM optimisation | |
@@ -77,9 +76,11 b' var IPython = (function (IPython) {' | |||||
77 | onKeyEvent: $.proxy(this.handle_keyevent,this) |
|
76 | onKeyEvent: $.proxy(this.handle_keyevent,this) | |
78 | }; |
|
77 | }; | |
79 |
|
78 | |||
80 |
|
|
79 | var config = this.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options}); | |
81 |
|
80 | Cell.apply(this,[{ | ||
82 | IPython.Cell.apply(this,[options]); |
|
81 | config: config, | |
|
82 | keyboard_manager: options.keyboard_manager, | |||
|
83 | events: this.events}]); | |||
83 |
|
84 | |||
84 | // Attributes we want to override in this subclass. |
|
85 | // Attributes we want to override in this subclass. | |
85 | this.cell_type = "code"; |
|
86 | this.cell_type = "code"; | |
@@ -109,29 +110,31 b' var IPython = (function (IPython) {' | |||||
109 |
|
110 | |||
110 | CodeCell.msg_cells = {}; |
|
111 | CodeCell.msg_cells = {}; | |
111 |
|
112 | |||
112 |
CodeCell.prototype = new |
|
113 | CodeCell.prototype = new Cell(); | |
113 |
|
114 | |||
114 | /** |
|
115 | /** | |
115 | * @method auto_highlight |
|
116 | * @method auto_highlight | |
116 | */ |
|
117 | */ | |
117 | CodeCell.prototype.auto_highlight = function () { |
|
118 | CodeCell.prototype.auto_highlight = function () { | |
118 |
this._auto_highlight( |
|
119 | this._auto_highlight(this.config.cell_magic_highlight); | |
119 | }; |
|
120 | }; | |
120 |
|
121 | |||
121 | /** @method create_element */ |
|
122 | /** @method create_element */ | |
122 | CodeCell.prototype.create_element = function () { |
|
123 | CodeCell.prototype.create_element = function () { | |
123 |
|
|
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 | cell.attr('tabindex','2'); |
|
127 | cell.attr('tabindex','2'); | |
127 |
|
128 | |||
128 | var input = $('<div></div>').addClass('input'); |
|
129 | var input = $('<div></div>').addClass('input'); | |
129 | var prompt = $('<div/>').addClass('prompt input_prompt'); |
|
130 | var prompt = $('<div/>').addClass('prompt input_prompt'); | |
130 | var inner_cell = $('<div/>').addClass('inner_cell'); |
|
131 | var inner_cell = $('<div/>').addClass('inner_cell'); | |
131 |
this.celltoolbar = new |
|
132 | this.celltoolbar = new celltoolbar.CellToolbar({ | |
|
133 | cell: this, | |||
|
134 | notebook: this.notebook}); | |||
132 | inner_cell.append(this.celltoolbar.element); |
|
135 | inner_cell.append(this.celltoolbar.element); | |
133 | var input_area = $('<div/>').addClass('input_area'); |
|
136 | var input_area = $('<div/>').addClass('input_area'); | |
134 | this.code_mirror = CodeMirror(input_area.get(0), this.cm_config); |
|
137 | this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config); | |
135 | $(this.code_mirror.getInputField()).attr("spellcheck", "false"); |
|
138 | $(this.code_mirror.getInputField()).attr("spellcheck", "false"); | |
136 | inner_cell.append(input_area); |
|
139 | inner_cell.append(input_area); | |
137 | input.append(prompt).append(inner_cell); |
|
140 | input.append(prompt).append(inner_cell); | |
@@ -158,13 +161,17 b' var IPython = (function (IPython) {' | |||||
158 | var output = $('<div></div>'); |
|
161 | var output = $('<div></div>'); | |
159 | cell.append(input).append(widget_area).append(output); |
|
162 | cell.append(input).append(widget_area).append(output); | |
160 | this.element = cell; |
|
163 | this.element = cell; | |
161 |
this.output_area = new |
|
164 | this.output_area = new outputarea.OutputArea({ | |
162 | this.completer = new IPython.Completer(this); |
|
165 | selector: output, | |
|
166 | prompt_area: true, | |||
|
167 | events: this.events, | |||
|
168 | keyboard_manager: this.keyboard_manager}); | |||
|
169 | this.completer = new completer.Completer(this, this.events); | |||
163 | }; |
|
170 | }; | |
164 |
|
171 | |||
165 | /** @method bind_events */ |
|
172 | /** @method bind_events */ | |
166 | CodeCell.prototype.bind_events = function () { |
|
173 | CodeCell.prototype.bind_events = function () { | |
167 |
|
|
174 | Cell.prototype.bind_events.apply(this); | |
168 | var that = this; |
|
175 | var that = this; | |
169 |
|
176 | |||
170 | this.element.focusout( |
|
177 | this.element.focusout( | |
@@ -187,7 +194,7 b' var IPython = (function (IPython) {' | |||||
187 | // they are sent, and remove tooltip if any, except for tab again |
|
194 | // they are sent, and remove tooltip if any, except for tab again | |
188 | var tooltip_closed = null; |
|
195 | var tooltip_closed = null; | |
189 | if (event.type === 'keydown' && event.which != keycodes.tab ) { |
|
196 | if (event.type === 'keydown' && event.which != keycodes.tab ) { | |
190 |
tooltip_closed = |
|
197 | tooltip_closed = this.tooltip.remove_and_cancel_tooltip(); | |
191 | } |
|
198 | } | |
192 |
|
199 | |||
193 | var cur = editor.getCursor(); |
|
200 | var cur = editor.getCursor(); | |
@@ -195,21 +202,21 b' var IPython = (function (IPython) {' | |||||
195 | this.auto_highlight(); |
|
202 | this.auto_highlight(); | |
196 | } |
|
203 | } | |
197 |
|
204 | |||
198 |
if (event.which === keycodes.down && event.type === 'keypress' && |
|
205 | if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) { | |
199 | // triger on keypress (!) otherwise inconsistent event.which depending on plateform |
|
206 | // triger on keypress (!) otherwise inconsistent event.which depending on plateform | |
200 | // browser and keyboard layout ! |
|
207 | // browser and keyboard layout ! | |
201 | // Pressing '(' , request tooltip, don't forget to reappend it |
|
208 | // Pressing '(' , request tooltip, don't forget to reappend it | |
202 | // The second argument says to hide the tooltip if the docstring |
|
209 | // The second argument says to hide the tooltip if the docstring | |
203 | // is actually empty |
|
210 | // is actually empty | |
204 |
|
|
211 | this.tooltip.pending(that, true); | |
205 | } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') { |
|
212 | } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') { | |
206 | // If tooltip is active, cancel it. The call to |
|
213 | // If tooltip is active, cancel it. The call to | |
207 | // remove_and_cancel_tooltip above doesn't pass, force=true. |
|
214 | // remove_and_cancel_tooltip above doesn't pass, force=true. | |
208 | // Because of this it won't actually close the tooltip |
|
215 | // Because of this it won't actually close the tooltip | |
209 | // if it is in sticky mode. Thus, we have to check again if it is open |
|
216 | // if it is in sticky mode. Thus, we have to check again if it is open | |
210 | // and close it with force=true. |
|
217 | // and close it with force=true. | |
211 |
if (! |
|
218 | if (!this.tooltip._hidden) { | |
212 |
|
|
219 | this.tooltip.remove_and_cancel_tooltip(true); | |
213 | } |
|
220 | } | |
214 | // If we closed the tooltip, don't let CM or the global handlers |
|
221 | // If we closed the tooltip, don't let CM or the global handlers | |
215 | // handle this event. |
|
222 | // handle this event. | |
@@ -223,12 +230,12 b' var IPython = (function (IPython) {' | |||||
223 | return false; |
|
230 | return false; | |
224 | } |
|
231 | } | |
225 | } |
|
232 | } | |
226 |
|
|
233 | this.tooltip.request(that); | |
227 | event.stop(); |
|
234 | event.stop(); | |
228 | return true; |
|
235 | return true; | |
229 | } else if (event.keyCode === keycodes.tab && event.type == 'keydown') { |
|
236 | } else if (event.keyCode === keycodes.tab && event.type == 'keydown') { | |
230 | // Tab completion. |
|
237 | // Tab completion. | |
231 |
|
|
238 | this.tooltip.remove_and_cancel_tooltip(); | |
232 | if (editor.somethingSelected()) { |
|
239 | if (editor.somethingSelected()) { | |
233 | return false; |
|
240 | return false; | |
234 | } |
|
241 | } | |
@@ -246,7 +253,7 b' var IPython = (function (IPython) {' | |||||
246 |
|
253 | |||
247 | // keyboard event wasn't one of those unique to code cells, let's see |
|
254 | // keyboard event wasn't one of those unique to code cells, let's see | |
248 | // if it's one of the generic ones (i.e. check edit mode shortcuts) |
|
255 | // if it's one of the generic ones (i.e. check edit mode shortcuts) | |
249 |
return |
|
256 | return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]); | |
250 | }; |
|
257 | }; | |
251 |
|
258 | |||
252 | // Kernel related calls. |
|
259 | // Kernel related calls. | |
@@ -305,7 +312,7 b' var IPython = (function (IPython) {' | |||||
305 | }; |
|
312 | }; | |
306 |
|
313 | |||
307 | CodeCell.prototype._open_with_pager = function (payload) { |
|
314 | CodeCell.prototype._open_with_pager = function (payload) { | |
308 |
|
|
315 | this.events.trigger('open_with_text.Pager', payload); | |
309 | }; |
|
316 | }; | |
310 |
|
317 | |||
311 | /** |
|
318 | /** | |
@@ -315,7 +322,7 b' var IPython = (function (IPython) {' | |||||
315 | CodeCell.prototype._handle_execute_reply = function (msg) { |
|
322 | CodeCell.prototype._handle_execute_reply = function (msg) { | |
316 | this.set_input_prompt(msg.content.execution_count); |
|
323 | this.set_input_prompt(msg.content.execution_count); | |
317 | this.element.removeClass("running"); |
|
324 | this.element.removeClass("running"); | |
318 |
|
|
325 | this.events.trigger('set_dirty.Notebook', {value: true}); | |
319 | }; |
|
326 | }; | |
320 |
|
327 | |||
321 | /** |
|
328 | /** | |
@@ -324,7 +331,7 b' var IPython = (function (IPython) {' | |||||
324 | */ |
|
331 | */ | |
325 | CodeCell.prototype._handle_set_next_input = function (payload) { |
|
332 | CodeCell.prototype._handle_set_next_input = function (payload) { | |
326 | var data = {'cell': this, 'text': payload.text}; |
|
333 | var data = {'cell': this, 'text': payload.text}; | |
327 |
|
|
334 | this.events.trigger('set_next_input.Notebook', data); | |
328 | }; |
|
335 | }; | |
329 |
|
336 | |||
330 | /** |
|
337 | /** | |
@@ -339,7 +346,7 b' var IPython = (function (IPython) {' | |||||
339 | // Basic cell manipulation. |
|
346 | // Basic cell manipulation. | |
340 |
|
347 | |||
341 | CodeCell.prototype.select = function () { |
|
348 | CodeCell.prototype.select = function () { | |
342 |
var cont = |
|
349 | var cont = Cell.prototype.select.apply(this); | |
343 | if (cont) { |
|
350 | if (cont) { | |
344 | this.code_mirror.refresh(); |
|
351 | this.code_mirror.refresh(); | |
345 | this.auto_highlight(); |
|
352 | this.auto_highlight(); | |
@@ -348,7 +355,7 b' var IPython = (function (IPython) {' | |||||
348 | }; |
|
355 | }; | |
349 |
|
356 | |||
350 | CodeCell.prototype.render = function () { |
|
357 | CodeCell.prototype.render = function () { | |
351 |
var cont = |
|
358 | var cont = Cell.prototype.render.apply(this); | |
352 | // Always execute, even if we are already in the rendered state |
|
359 | // Always execute, even if we are already in the rendered state | |
353 | return cont; |
|
360 | return cont; | |
354 | }; |
|
361 | }; | |
@@ -451,7 +458,7 b' var IPython = (function (IPython) {' | |||||
451 | // JSON serialization |
|
458 | // JSON serialization | |
452 |
|
459 | |||
453 | CodeCell.prototype.fromJSON = function (data) { |
|
460 | CodeCell.prototype.fromJSON = function (data) { | |
454 |
|
|
461 | Cell.prototype.fromJSON.apply(this, arguments); | |
455 | if (data.cell_type === 'code') { |
|
462 | if (data.cell_type === 'code') { | |
456 | if (data.input !== undefined) { |
|
463 | if (data.input !== undefined) { | |
457 | this.set_text(data.input); |
|
464 | this.set_text(data.input); | |
@@ -479,7 +486,7 b' var IPython = (function (IPython) {' | |||||
479 |
|
486 | |||
480 |
|
487 | |||
481 | CodeCell.prototype.toJSON = function () { |
|
488 | CodeCell.prototype.toJSON = function () { | |
482 |
var data = |
|
489 | var data = Cell.prototype.toJSON.apply(this); | |
483 | data.input = this.get_text(); |
|
490 | data.input = this.get_text(); | |
484 | // is finite protect against undefined and '*' value |
|
491 | // is finite protect against undefined and '*' value | |
485 | if (isFinite(this.input_prompt_number)) { |
|
492 | if (isFinite(this.input_prompt_number)) { | |
@@ -499,11 +506,11 b' var IPython = (function (IPython) {' | |||||
499 | * @return is the action being taken |
|
506 | * @return is the action being taken | |
500 | */ |
|
507 | */ | |
501 | CodeCell.prototype.unselect = function () { |
|
508 | CodeCell.prototype.unselect = function () { | |
502 |
var cont = |
|
509 | var cont = Cell.prototype.unselect.apply(this); | |
503 | if (cont) { |
|
510 | if (cont) { | |
504 | // When a code cell is usnelected, make sure that the corresponding |
|
511 | // When a code cell is usnelected, make sure that the corresponding | |
505 | // tooltip and completer to that cell is closed. |
|
512 | // tooltip and completer to that cell is closed. | |
506 |
|
|
513 | this.tooltip.remove_and_cancel_tooltip(true); | |
507 | if (this.completer !== null) { |
|
514 | if (this.completer !== null) { | |
508 | this.completer.close(); |
|
515 | this.completer.close(); | |
509 | } |
|
516 | } | |
@@ -511,7 +518,8 b' var IPython = (function (IPython) {' | |||||
511 | return cont; |
|
518 | return cont; | |
512 | }; |
|
519 | }; | |
513 |
|
520 | |||
|
521 | // Backwards compatability. | |||
514 | IPython.CodeCell = CodeCell; |
|
522 | IPython.CodeCell = CodeCell; | |
515 |
|
523 | |||
516 | return IPython; |
|
524 | return {'CodeCell': CodeCell}; | |
517 | }(IPython)); |
|
525 | }); |
@@ -7,10 +7,15 b" CodeMirror.requireMode('python',function(){" | |||||
7 | "use strict"; |
|
7 | "use strict"; | |
8 |
|
8 | |||
9 | CodeMirror.defineMode("ipython", function(conf, parserConf) { |
|
9 | CodeMirror.defineMode("ipython", function(conf, parserConf) { | |
10 |
|
10 | var pythonConf = {}; | ||
11 | parserConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]"); |
|
11 | for (var prop in parserConf) { | |
12 | parserConf.name = 'python' |
|
12 | if (parserConf.hasOwnProperty(prop)) { | |
13 | return CodeMirror.getMode(conf, parserConf); |
|
13 | pythonConf[prop] = parserConf[prop]; | |
|
14 | } | |||
|
15 | } | |||
|
16 | pythonConf.name = 'python'; | |||
|
17 | pythonConf.singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]"); | |||
|
18 | return CodeMirror.getMode(conf, pythonConf); | |||
14 | }, 'python'); |
|
19 | }, 'python'); | |
15 |
|
20 | |||
16 | CodeMirror.defineMIME("text/x-ipython", "ipython"); |
|
21 | CodeMirror.defineMIME("text/x-ipython", "ipython"); |
@@ -8,7 +8,6 b'' | |||||
8 |
|
8 | |||
9 | CodeMirror.requireMode('gfm', function(){ |
|
9 | CodeMirror.requireMode('gfm', function(){ | |
10 | CodeMirror.requireMode('stex', function(){ |
|
10 | CodeMirror.requireMode('stex', function(){ | |
11 | console.log('defining custom mode...'); |
|
|||
12 | CodeMirror.defineMode("ipythongfm", function(config, parserConfig) { |
|
11 | CodeMirror.defineMode("ipythongfm", function(config, parserConfig) { | |
13 |
|
12 | |||
14 | var gfm_mode = CodeMirror.getMode(config, "gfm"); |
|
13 | var gfm_mode = CodeMirror.getMode(config, "gfm"); |
@@ -1,17 +1,17 b'' | |||||
1 | // Copyright (c) IPython Development Team. |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Distributed under the terms of the Modified BSD License. |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 |
|
3 | |||
4 | // Completer |
|
4 | define([ | |
5 | // |
|
5 | 'base/js/namespace', | |
6 | // Completer is be a class that takes a cell instance. |
|
6 | 'jquery', | |
7 |
|
7 | 'base/js/utils', | ||
8 | var IPython = (function (IPython) { |
|
8 | 'base/js/keyboard', | |
9 | // that will prevent us from misspelling |
|
9 | 'notebook/js/contexthint', | |
|
10 | ], function(IPython, $, utils, keyboard) { | |||
10 | "use strict"; |
|
11 | "use strict"; | |
11 |
|
12 | |||
12 | // easier key mapping |
|
13 | // easier key mapping | |
13 |
var keycodes = |
|
14 | var keycodes = keyboard.keycodes; | |
14 | var utils = IPython.utils; |
|
|||
15 |
|
15 | |||
16 | var prepend_n_prc = function(str, n) { |
|
16 | var prepend_n_prc = function(str, n) { | |
17 | for( var i =0 ; i< n ; i++){ |
|
17 | for( var i =0 ; i< n ; i++){ | |
@@ -78,14 +78,14 b' var IPython = (function (IPython) {' | |||||
78 | } |
|
78 | } | |
79 |
|
79 | |||
80 |
|
80 | |||
81 | var Completer = function (cell) { |
|
81 | var Completer = function (cell, events) { | |
82 | this.cell = cell; |
|
82 | this.cell = cell; | |
83 | this.editor = cell.code_mirror; |
|
83 | this.editor = cell.code_mirror; | |
84 | var that = this; |
|
84 | var that = this; | |
85 |
|
|
85 | events.on('status_busy.Kernel', function () { | |
86 | that.skip_kernel_completion = true; |
|
86 | that.skip_kernel_completion = true; | |
87 | }); |
|
87 | }); | |
88 |
|
|
88 | events.on('status_idle.Kernel', function () { | |
89 | that.skip_kernel_completion = false; |
|
89 | that.skip_kernel_completion = false; | |
90 | }); |
|
90 | }); | |
91 | }; |
|
91 | }; | |
@@ -351,6 +351,18 b' var IPython = (function (IPython) {' | |||||
351 | } |
|
351 | } | |
352 | index = Math.min(Math.max(index, 0), options.length-1); |
|
352 | index = Math.min(Math.max(index, 0), options.length-1); | |
353 | this.sel[0].selectedIndex = index; |
|
353 | this.sel[0].selectedIndex = index; | |
|
354 | } else if (code == keycodes.pageup || code == keycodes.pagedown) { | |||
|
355 | CodeMirror.e_stop(event); | |||
|
356 | ||||
|
357 | var options = this.sel.find('option'); | |||
|
358 | var index = this.sel[0].selectedIndex; | |||
|
359 | if (code == keycodes.pageup) { | |||
|
360 | index -= 10; // As 10 is the hard coded size of the drop down menu | |||
|
361 | } else { | |||
|
362 | index += 10; | |||
|
363 | } | |||
|
364 | index = Math.min(Math.max(index, 0), options.length-1); | |||
|
365 | this.sel[0].selectedIndex = index; | |||
354 | } else if (code == keycodes.left || code == keycodes.right) { |
|
366 | } else if (code == keycodes.left || code == keycodes.right) { | |
355 | this.close(); |
|
367 | this.close(); | |
356 | } |
|
368 | } | |
@@ -379,7 +391,9 b' var IPython = (function (IPython) {' | |||||
379 | that.carry_on_completion(); |
|
391 | that.carry_on_completion(); | |
380 | }, 50); |
|
392 | }, 50); | |
381 | }; |
|
393 | }; | |
|
394 | ||||
|
395 | // For backwards compatability. | |||
382 | IPython.Completer = Completer; |
|
396 | IPython.Completer = Completer; | |
383 |
|
397 | |||
384 | return IPython; |
|
398 | return {'Completer': Completer}; | |
385 | }(IPython)); |
|
399 | }); |
@@ -1,28 +1,9 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2012 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | define([], function() { | |
9 | // Notebook |
|
|||
10 | //============================================================================ |
|
|||
11 |
|
||||
12 | /** |
|
|||
13 | * @module IPython |
|
|||
14 | * @namespace IPython |
|
|||
15 | **/ |
|
|||
16 |
|
||||
17 | var IPython = (function (IPython) { |
|
|||
18 | "use strict"; |
|
5 | "use strict"; | |
19 | /** |
|
6 | ||
20 | * A place where some stuff can be confugured. |
|
|||
21 | * |
|
|||
22 | * @class config |
|
|||
23 | * @static |
|
|||
24 | * |
|
|||
25 | **/ |
|
|||
26 | var default_config = { |
|
7 | var default_config = { | |
27 | /** |
|
8 | /** | |
28 | * Dictionary of object to autodetect highlight mode for code cell. |
|
9 | * Dictionary of object to autodetect highlight mode for code cell. | |
@@ -50,30 +31,25 b' var IPython = (function (IPython) {' | |||||
50 | * cell_magic_highlight['javascript'] = {'reg':[/^var/]} |
|
31 | * cell_magic_highlight['javascript'] = {'reg':[/^var/]} | |
51 | */ |
|
32 | */ | |
52 | cell_magic_highlight : { |
|
33 | cell_magic_highlight : { | |
53 |
|
|
34 | 'magic_javascript' :{'reg':[/^%%javascript/]}, | |
54 |
|
|
35 | 'magic_perl' :{'reg':[/^%%perl/]}, | |
55 |
|
|
36 | 'magic_ruby' :{'reg':[/^%%ruby/]}, | |
56 |
|
|
37 | 'magic_python' :{'reg':[/^%%python3?/]}, | |
57 |
|
|
38 | 'magic_shell' :{'reg':[/^%%bash/]}, | |
58 |
|
|
39 | 'magic_r' :{'reg':[/^%%R/]}, | |
59 |
|
|
40 | 'magic_text/x-cython' :{'reg':[/^%%cython/]}, | |
60 |
|
|
41 | }, | |
61 |
|
42 | |||
62 | /** |
|
43 | /** | |
63 | * same as `cell_magic_highlight` but for raw cells |
|
44 | * same as `cell_magic_highlight` but for raw cells | |
64 | * @attribute raw_cell_highlight |
|
45 | * @attribute raw_cell_highlight | |
65 | */ |
|
46 | */ | |
66 | raw_cell_highlight : { |
|
47 | raw_cell_highlight : { | |
67 |
|
|
48 | 'diff' :{'reg':[/^diff/]} | |
68 |
|
|
49 | }, | |
69 |
|
50 | }; | ||
70 | }; |
|
51 | ||
71 |
|
52 | return { | ||
72 | // use the same method to merge user configuration |
|
53 | 'default_config': default_config, | |
73 | IPython.config = {}; |
|
54 | }; | |
74 | $.extend(IPython.config, default_config); |
|
55 | }); | |
75 |
|
||||
76 | return IPython; |
|
|||
77 |
|
||||
78 | }(IPython)); |
|
|||
79 |
|
@@ -1,12 +1,15 b'' | |||||
|
1 | // Copyright (c) IPython Development Team. | |||
|
2 | // Distributed under the terms of the Modified BSD License. | |||
|
3 | ||||
1 | // highly adapted for codemiror jshint |
|
4 | // highly adapted for codemiror jshint | |
2 |
(function |
|
5 | define([], function() { | |
3 | "use strict"; |
|
6 | "use strict"; | |
4 |
|
7 | |||
5 |
|
|
8 | var forEach = function(arr, f) { | |
6 | for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); |
|
9 | for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); | |
7 | } |
|
10 | }; | |
8 |
|
11 | |||
9 |
|
|
12 | var arrayContains = function(arr, item) { | |
10 | if (!Array.prototype.indexOf) { |
|
13 | if (!Array.prototype.indexOf) { | |
11 | var i = arr.length; |
|
14 | var i = arr.length; | |
12 | while (i--) { |
|
15 | while (i--) { | |
@@ -17,7 +20,7 b'' | |||||
17 | return false; |
|
20 | return false; | |
18 | } |
|
21 | } | |
19 | return arr.indexOf(item) != -1; |
|
22 | return arr.indexOf(item) != -1; | |
20 | } |
|
23 | }; | |
21 |
|
24 | |||
22 | CodeMirror.contextHint = function (editor) { |
|
25 | CodeMirror.contextHint = function (editor) { | |
23 | // Find the token at the cursor |
|
26 | // Find the token at the cursor | |
@@ -26,7 +29,7 b'' | |||||
26 | tprop = token; |
|
29 | tprop = token; | |
27 | // If it's not a 'word-style' token, ignore the token. |
|
30 | // If it's not a 'word-style' token, ignore the token. | |
28 | // If it is a property, find out what it is a property of. |
|
31 | // If it is a property, find out what it is a property of. | |
29 |
var list = |
|
32 | var list = []; | |
30 | var clist = getCompletions(token, editor); |
|
33 | var clist = getCompletions(token, editor); | |
31 | for (var i = 0; i < clist.length; i++) { |
|
34 | for (var i = 0; i < clist.length; i++) { | |
32 | list.push({ |
|
35 | list.push({ | |
@@ -40,55 +43,56 b'' | |||||
40 | line: cur.line, |
|
43 | line: cur.line, | |
41 | ch: token.end |
|
44 | ch: token.end | |
42 | } |
|
45 | } | |
43 | }) |
|
46 | }); | |
44 | } |
|
47 | } | |
45 | return list; |
|
48 | return list; | |
46 | } |
|
49 | }; | |
47 |
|
50 | |||
48 | // find all 'words' of current cell |
|
51 | // find all 'words' of current cell | |
49 | var getAllTokens = function (editor) { |
|
52 | var getAllTokens = function (editor) { | |
50 |
|
|
53 | var found = []; | |
51 |
|
54 | |||
52 |
|
|
55 | // add to found if not already in it | |
53 |
|
56 | |||
54 |
|
57 | |||
55 |
|
|
58 | function maybeAdd(str) { | |
56 |
|
|
59 | if (!arrayContains(found, str)) found.push(str); | |
57 |
|
|
60 | } | |
58 |
|
61 | |||
59 |
|
|
62 | // loop through all token on all lines | |
60 |
|
|
63 | var lineCount = editor.lineCount(); | |
61 |
|
|
64 | // loop on line | |
62 |
|
|
65 | for (var l = 0; l < lineCount; l++) { | |
63 |
|
|
66 | var line = editor.getLine(l); | |
64 |
|
|
67 | //loop on char | |
65 |
|
|
68 | for (var c = 1; c < line.length; c++) { | |
66 |
|
|
69 | var tk = editor.getTokenAt({ | |
67 |
|
|
70 | line: l, | |
68 |
|
|
71 | ch: c | |
69 |
|
|
72 | }); | |
70 |
|
|
73 | // if token has a class, it has geat chances of beeing | |
71 |
|
|
74 | // of interest. Add it to the list of possible completions. | |
72 |
|
|
75 | // we could skip token of ClassName 'comment' | |
73 |
|
|
76 | // or 'number' and 'operator' | |
74 |
|
|
77 | if (tk.className !== null) { | |
75 |
|
|
78 | maybeAdd(tk.string); | |
76 | } |
|
|||
77 | // jump to char after end of current token |
|
|||
78 | c = tk.end; |
|
|||
79 | } |
|
79 | } | |
|
80 | // jump to char after end of current token | |||
|
81 | c = tk.end; | |||
80 | } |
|
82 | } | |
81 | return found; |
|
|||
82 | } |
|
83 | } | |
|
84 | return found; | |||
|
85 | }; | |||
83 |
|
86 | |||
84 |
|
87 | var getCompletions = function(token, editor) { | ||
85 | function getCompletions(token, editor) { |
|
|||
86 | var candidates = getAllTokens(editor); |
|
88 | var candidates = getAllTokens(editor); | |
87 | // filter all token that have a common start (but nox exactly) the lenght of the current token |
|
89 | // filter all token that have a common start (but nox exactly) the lenght of the current token | |
88 | var lambda = function (x) { |
|
90 | var lambda = function (x) { | |
89 | return (x.indexOf(token.string) == 0 && x != token.string) |
|
91 | return (x.indexOf(token.string) === 0 && x != token.string); | |
90 | }; |
|
92 | }; | |
91 | var filterd = candidates.filter(lambda); |
|
93 | var filterd = candidates.filter(lambda); | |
92 | return filterd; |
|
94 | return filterd; | |
93 | } |
|
95 | }; | |
94 | })(); |
|
96 | ||
|
97 | return {'contextHint': CodeMirror.contextHint}; | |||
|
98 | }); |
This diff has been collapsed as it changes many lines, (902 lines changed) Show them Hide them | |||||
@@ -1,468 +1,476 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | define([ | |
9 | // Keyboard management |
|
5 | 'base/js/namespace', | |
10 | //============================================================================ |
|
6 | 'jquery', | |
11 |
|
7 | 'base/js/utils', | ||
12 | var IPython = (function (IPython) { |
|
8 | 'base/js/keyboard', | |
|
9 | ], function(IPython, $, utils, keyboard) { | |||
13 | "use strict"; |
|
10 | "use strict"; | |
|
11 | ||||
|
12 | var browser = utils.browser[0]; | |||
|
13 | var platform = utils.platform; | |||
14 |
|
14 | |||
15 | var browser = IPython.utils.browser[0]; |
|
15 | // Main keyboard manager for the notebook | |
16 | var platform = IPython.utils.platform; |
|
16 | var keycodes = keyboard.keycodes; | |
17 |
|
17 | |||
18 | // Default keyboard shortcuts |
|
18 | var KeyboardManager = function (options) { | |
|
19 | // Constructor | |||
|
20 | // | |||
|
21 | // Parameters: | |||
|
22 | // options: dictionary | |||
|
23 | // Dictionary of keyword arguments. | |||
|
24 | // events: $(Events) instance | |||
|
25 | // pager: Pager instance | |||
|
26 | this.mode = 'command'; | |||
|
27 | this.enabled = true; | |||
|
28 | this.pager = options.pager; | |||
|
29 | this.quick_help = undefined; | |||
|
30 | this.notebook = undefined; | |||
|
31 | this.bind_events(); | |||
|
32 | this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events); | |||
|
33 | this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts()); | |||
|
34 | this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts()); | |||
|
35 | this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events); | |||
|
36 | this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts()); | |||
|
37 | this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts()); | |||
|
38 | }; | |||
19 |
|
39 | |||
20 |
|
|
40 | KeyboardManager.prototype.get_default_common_shortcuts = function() { | |
21 | 'shift' : { |
|
41 | var that = this; | |
22 | help : '', |
|
42 | var shortcuts = { | |
23 |
|
|
43 | 'shift' : { | |
24 | handler : function (event) { |
|
44 | help : '', | |
25 | // ignore shift keydown |
|
45 | help_index : '', | |
26 | return true; |
|
46 | handler : function (event) { | |
27 | } |
|
47 | // ignore shift keydown | |
28 | }, |
|
48 | return true; | |
29 | 'shift-enter' : { |
|
49 | } | |
30 | help : 'run cell, select below', |
|
50 | }, | |
31 |
|
|
51 | 'shift-enter' : { | |
32 | handler : function (event) { |
|
52 | help : 'run cell, select below', | |
33 | IPython.notebook.execute_cell_and_select_below(); |
|
53 | help_index : 'ba', | |
34 | return false; |
|
54 | handler : function (event) { | |
35 | } |
|
55 | that.notebook.execute_cell_and_select_below(); | |
36 | }, |
|
56 | return false; | |
37 | 'ctrl-enter' : { |
|
57 | } | |
38 | help : 'run cell', |
|
58 | }, | |
39 |
|
|
59 | 'ctrl-enter' : { | |
40 | handler : function (event) { |
|
60 | help : 'run cell', | |
41 | IPython.notebook.execute_cell(); |
|
61 | help_index : 'bb', | |
42 | return false; |
|
62 | handler : function (event) { | |
43 | } |
|
63 | that.notebook.execute_cell(); | |
44 | }, |
|
64 | return false; | |
45 | 'alt-enter' : { |
|
65 | } | |
46 | help : 'run cell, insert below', |
|
66 | }, | |
47 |
|
|
67 | 'alt-enter' : { | |
48 | handler : function (event) { |
|
68 | help : 'run cell, insert below', | |
49 | IPython.notebook.execute_cell_and_insert_below(); |
|
69 | help_index : 'bc', | |
50 | return false; |
|
70 | handler : function (event) { | |
|
71 | that.notebook.execute_cell_and_insert_below(); | |||
|
72 | return false; | |||
|
73 | } | |||
51 | } |
|
74 | } | |
|
75 | }; | |||
|
76 | ||||
|
77 | if (platform === 'MacOS') { | |||
|
78 | shortcuts['cmd-s'] = | |||
|
79 | { | |||
|
80 | help : 'save notebook', | |||
|
81 | help_index : 'fb', | |||
|
82 | handler : function (event) { | |||
|
83 | that.notebook.save_checkpoint(); | |||
|
84 | event.preventDefault(); | |||
|
85 | return false; | |||
|
86 | } | |||
|
87 | }; | |||
|
88 | } else { | |||
|
89 | shortcuts['ctrl-s'] = | |||
|
90 | { | |||
|
91 | help : 'save notebook', | |||
|
92 | help_index : 'fb', | |||
|
93 | handler : function (event) { | |||
|
94 | that.notebook.save_checkpoint(); | |||
|
95 | event.preventDefault(); | |||
|
96 | return false; | |||
|
97 | } | |||
|
98 | }; | |||
52 | } |
|
99 | } | |
|
100 | return shortcuts; | |||
53 | }; |
|
101 | }; | |
54 |
|
102 | |||
55 | if (platform === 'MacOS') { |
|
103 | KeyboardManager.prototype.get_default_edit_shortcuts = function() { | |
56 | default_common_shortcuts['cmd-s'] = |
|
104 | var that = this; | |
57 |
|
|
105 | return { | |
58 | help : 'save notebook', |
|
106 | 'esc' : { | |
59 |
help |
|
107 | help : 'command mode', | |
|
108 | help_index : 'aa', | |||
60 | handler : function (event) { |
|
109 | handler : function (event) { | |
61 |
|
|
110 | that.notebook.command_mode(); | |
62 | event.preventDefault(); |
|
|||
63 | return false; |
|
111 | return false; | |
64 | } |
|
112 | } | |
65 |
} |
|
113 | }, | |
66 | } else { |
|
114 | 'ctrl-m' : { | |
67 | default_common_shortcuts['ctrl-s'] = |
|
115 | help : 'command mode', | |
68 | { |
|
116 | help_index : 'ab', | |
69 | help : 'save notebook', |
|
|||
70 | help_index : 'fb', |
|
|||
71 | handler : function (event) { |
|
117 | handler : function (event) { | |
72 |
|
|
118 | that.notebook.command_mode(); | |
73 | event.preventDefault(); |
|
|||
74 | return false; |
|
119 | return false; | |
75 | } |
|
120 | } | |
76 |
} |
|
121 | }, | |
77 | } |
|
122 | 'up' : { | |
78 |
|
123 | help : '', | ||
79 | // Edit mode defaults |
|
124 | help_index : '', | |
80 |
|
125 | handler : function (event) { | ||
81 | var default_edit_shortcuts = { |
|
126 | var index = that.notebook.get_selected_index(); | |
82 | 'esc' : { |
|
127 | var cell = that.notebook.get_cell(index); | |
83 | help : 'command mode', |
|
128 | if (cell && cell.at_top() && index !== 0) { | |
84 | help_index : 'aa', |
|
129 | event.preventDefault(); | |
85 | handler : function (event) { |
|
130 | that.notebook.command_mode(); | |
86 |
|
|
131 | that.notebook.select_prev(); | |
87 | return false; |
|
132 | that.notebook.edit_mode(); | |
88 | } |
|
133 | var cm = that.notebook.get_selected_cell().code_mirror; | |
89 | }, |
|
134 | cm.setCursor(cm.lastLine(), 0); | |
90 | 'ctrl-m' : { |
|
135 | return false; | |
91 | help : 'command mode', |
|
136 | } else if (cell) { | |
92 | help_index : 'ab', |
|
137 | var cm = cell.code_mirror; | |
93 | handler : function (event) { |
|
138 | cm.execCommand('goLineUp'); | |
94 | IPython.notebook.command_mode(); |
|
139 | return false; | |
95 |
|
|
140 | } | |
96 | } |
|
141 | } | |
97 | }, |
|
142 | }, | |
98 |
|
|
143 | 'down' : { | |
99 | help : '', |
|
144 | help : '', | |
100 | help_index : '', |
|
145 | help_index : '', | |
101 | handler : function (event) { |
|
146 | handler : function (event) { | |
102 |
var index = |
|
147 | var index = that.notebook.get_selected_index(); | |
103 |
var cell = |
|
148 | var cell = that.notebook.get_cell(index); | |
104 |
if (cell |
|
149 | if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) { | |
105 | event.preventDefault(); |
|
150 | event.preventDefault(); | |
106 |
|
|
151 | that.notebook.command_mode(); | |
107 |
|
|
152 | that.notebook.select_next(); | |
108 |
|
|
153 | that.notebook.edit_mode(); | |
109 |
var cm = |
|
154 | var cm = that.notebook.get_selected_cell().code_mirror; | |
110 |
cm.setCursor( |
|
155 | cm.setCursor(0, 0); | |
111 | return false; |
|
156 | return false; | |
112 |
} else |
|
157 | } else { | |
113 | var cm = cell.code_mirror; |
|
158 | var cm = cell.code_mirror; | |
114 |
cm.execCommand('goLine |
|
159 | cm.execCommand('goLineDown'); | |
|
160 | return false; | |||
|
161 | } | |||
|
162 | } | |||
|
163 | }, | |||
|
164 | 'ctrl-shift--' : { | |||
|
165 | help : 'split cell', | |||
|
166 | help_index : 'ea', | |||
|
167 | handler : function (event) { | |||
|
168 | that.notebook.split_cell(); | |||
115 | return false; |
|
169 | return false; | |
116 | } |
|
170 | } | |
117 | } |
|
171 | }, | |
118 | }, |
|
172 | 'ctrl-shift-subtract' : { | |
119 | 'down' : { |
|
173 | help : '', | |
120 |
|
|
174 | help_index : 'eb', | |
121 | help_index : '', |
|
175 | handler : function (event) { | |
122 | handler : function (event) { |
|
176 | that.notebook.split_cell(); | |
123 | var index = IPython.notebook.get_selected_index(); |
|
|||
124 | var cell = IPython.notebook.get_cell(index); |
|
|||
125 | if (cell.at_bottom() && index !== (IPython.notebook.ncells()-1)) { |
|
|||
126 | event.preventDefault(); |
|
|||
127 | IPython.notebook.command_mode(); |
|
|||
128 | IPython.notebook.select_next(); |
|
|||
129 | IPython.notebook.edit_mode(); |
|
|||
130 | var cm = IPython.notebook.get_selected_cell().code_mirror; |
|
|||
131 | cm.setCursor(0, 0); |
|
|||
132 | return false; |
|
|||
133 | } else { |
|
|||
134 | var cm = cell.code_mirror; |
|
|||
135 | cm.execCommand('goLineDown'); |
|
|||
136 | return false; |
|
177 | return false; | |
137 | } |
|
178 | } | |
138 | } |
|
179 | }, | |
139 |
} |
|
180 | }; | |
140 | 'ctrl-shift--' : { |
|
|||
141 | help : 'split cell', |
|
|||
142 | help_index : 'ea', |
|
|||
143 | handler : function (event) { |
|
|||
144 | IPython.notebook.split_cell(); |
|
|||
145 | return false; |
|
|||
146 | } |
|
|||
147 | }, |
|
|||
148 | 'ctrl-shift-subtract' : { |
|
|||
149 | help : '', |
|
|||
150 | help_index : 'eb', |
|
|||
151 | handler : function (event) { |
|
|||
152 | IPython.notebook.split_cell(); |
|
|||
153 | return false; |
|
|||
154 | } |
|
|||
155 | }, |
|
|||
156 | }; |
|
181 | }; | |
157 |
|
182 | |||
158 | // Command mode defaults |
|
183 | KeyboardManager.prototype.get_default_command_shortcuts = function() { | |
159 |
|
184 | var that = this; | ||
160 | var default_command_shortcuts = { |
|
185 | return { | |
161 | 'enter' : { |
|
186 | 'enter' : { | |
162 | help : 'edit mode', |
|
187 | help : 'edit mode', | |
163 | help_index : 'aa', |
|
188 | help_index : 'aa', | |
164 | handler : function (event) { |
|
189 | handler : function (event) { | |
165 |
|
|
190 | that.notebook.edit_mode(); | |
166 | return false; |
|
191 | return false; | |
167 | } |
|
|||
168 | }, |
|
|||
169 | 'up' : { |
|
|||
170 | help : 'select previous cell', |
|
|||
171 | help_index : 'da', |
|
|||
172 | handler : function (event) { |
|
|||
173 | var index = IPython.notebook.get_selected_index(); |
|
|||
174 | if (index !== 0 && index !== null) { |
|
|||
175 | IPython.notebook.select_prev(); |
|
|||
176 | IPython.notebook.focus_cell(); |
|
|||
177 | } |
|
192 | } | |
178 | return false; |
|
193 | }, | |
179 |
|
|
194 | 'up' : { | |
180 | }, |
|
195 | help : 'select previous cell', | |
181 | 'down' : { |
|
196 | help_index : 'da', | |
182 | help : 'select next cell', |
|
197 | handler : function (event) { | |
183 | help_index : 'db', |
|
198 | var index = that.notebook.get_selected_index(); | |
184 | handler : function (event) { |
|
199 | if (index !== 0 && index !== null) { | |
185 |
|
|
200 | that.notebook.select_prev(); | |
186 | if (index !== (IPython.notebook.ncells()-1) && index !== null) { |
|
201 | that.notebook.focus_cell(); | |
187 | IPython.notebook.select_next(); |
|
202 | } | |
188 | IPython.notebook.focus_cell(); |
|
203 | return false; | |
189 | } |
|
204 | } | |
190 | return false; |
|
205 | }, | |
191 |
|
|
206 | 'down' : { | |
192 | }, |
|
207 | help : 'select next cell', | |
193 | 'k' : { |
|
208 | help_index : 'db', | |
194 | help : 'select previous cell', |
|
209 | handler : function (event) { | |
195 | help_index : 'dc', |
|
210 | var index = that.notebook.get_selected_index(); | |
196 | handler : function (event) { |
|
211 | if (index !== (that.notebook.ncells()-1) && index !== null) { | |
197 |
|
|
212 | that.notebook.select_next(); | |
198 | if (index !== 0 && index !== null) { |
|
213 | that.notebook.focus_cell(); | |
199 | IPython.notebook.select_prev(); |
|
214 | } | |
200 | IPython.notebook.focus_cell(); |
|
215 | return false; | |
201 | } |
|
216 | } | |
202 | return false; |
|
217 | }, | |
203 |
|
|
218 | 'k' : { | |
204 | }, |
|
219 | help : 'select previous cell', | |
205 | 'j' : { |
|
220 | help_index : 'dc', | |
206 | help : 'select next cell', |
|
221 | handler : function (event) { | |
207 | help_index : 'dd', |
|
222 | var index = that.notebook.get_selected_index(); | |
208 | handler : function (event) { |
|
223 | if (index !== 0 && index !== null) { | |
209 |
|
|
224 | that.notebook.select_prev(); | |
210 | if (index !== (IPython.notebook.ncells()-1) && index !== null) { |
|
225 | that.notebook.focus_cell(); | |
211 | IPython.notebook.select_next(); |
|
226 | } | |
212 | IPython.notebook.focus_cell(); |
|
227 | return false; | |
213 | } |
|
228 | } | |
214 | return false; |
|
229 | }, | |
215 |
|
|
230 | 'j' : { | |
216 | }, |
|
231 | help : 'select next cell', | |
217 | 'x' : { |
|
232 | help_index : 'dd', | |
218 | help : 'cut cell', |
|
233 | handler : function (event) { | |
219 | help_index : 'ee', |
|
234 | var index = that.notebook.get_selected_index(); | |
220 | handler : function (event) { |
|
235 | if (index !== (that.notebook.ncells()-1) && index !== null) { | |
221 |
|
|
236 | that.notebook.select_next(); | |
222 | return false; |
|
237 | that.notebook.focus_cell(); | |
223 | } |
|
238 | } | |
224 | }, |
|
239 | return false; | |
225 |
|
|
240 | } | |
226 | help : 'copy cell', |
|
241 | }, | |
227 |
|
|
242 | 'x' : { | |
228 | handler : function (event) { |
|
243 | help : 'cut cell', | |
229 | IPython.notebook.copy_cell(); |
|
244 | help_index : 'ee', | |
230 | return false; |
|
245 | handler : function (event) { | |
231 | } |
|
246 | that.notebook.cut_cell(); | |
232 | }, |
|
247 | return false; | |
233 | 'shift-v' : { |
|
248 | } | |
234 | help : 'paste cell above', |
|
249 | }, | |
235 |
|
|
250 | 'c' : { | |
236 | handler : function (event) { |
|
251 | help : 'copy cell', | |
237 | IPython.notebook.paste_cell_above(); |
|
252 | help_index : 'ef', | |
238 | return false; |
|
253 | handler : function (event) { | |
239 | } |
|
254 | that.notebook.copy_cell(); | |
240 | }, |
|
255 | return false; | |
241 |
|
|
256 | } | |
242 | help : 'paste cell below', |
|
257 | }, | |
243 |
|
|
258 | 'shift-v' : { | |
244 | handler : function (event) { |
|
259 | help : 'paste cell above', | |
245 | IPython.notebook.paste_cell_below(); |
|
260 | help_index : 'eg', | |
246 | return false; |
|
261 | handler : function (event) { | |
247 | } |
|
262 | that.notebook.paste_cell_above(); | |
248 | }, |
|
263 | return false; | |
249 |
|
|
264 | } | |
250 | help : 'delete cell (press twice)', |
|
265 | }, | |
251 |
|
|
266 | 'v' : { | |
252 | count: 2, |
|
267 | help : 'paste cell below', | |
253 | handler : function (event) { |
|
268 | help_index : 'eh', | |
254 | IPython.notebook.delete_cell(); |
|
269 | handler : function (event) { | |
255 | return false; |
|
270 | that.notebook.paste_cell_below(); | |
256 | } |
|
271 | return false; | |
257 |
} |
|
272 | } | |
258 |
|
|
273 | }, | |
259 | help : 'insert cell above', |
|
274 | 'd' : { | |
260 | help_index : 'ec', |
|
275 | help : 'delete cell (press twice)', | |
261 | handler : function (event) { |
|
276 | help_index : 'ej', | |
262 | IPython.notebook.insert_cell_above(); |
|
277 | count: 2, | |
263 | IPython.notebook.select_prev(); |
|
278 | handler : function (event) { | |
264 |
|
|
279 | that.notebook.delete_cell(); | |
265 | return false; |
|
280 | return false; | |
266 | } |
|
281 | } | |
267 | }, |
|
282 | }, | |
268 |
|
|
283 | 'a' : { | |
269 |
help : 'insert cell |
|
284 | help : 'insert cell above', | |
270 |
help_index : 'e |
|
285 | help_index : 'ec', | |
271 | handler : function (event) { |
|
286 | handler : function (event) { | |
272 |
|
|
287 | that.notebook.insert_cell_above(); | |
273 |
|
|
288 | that.notebook.select_prev(); | |
274 |
|
|
289 | that.notebook.focus_cell(); | |
275 | return false; |
|
290 | return false; | |
276 | } |
|
291 | } | |
277 | }, |
|
292 | }, | |
278 |
|
|
293 | 'b' : { | |
279 |
help : 't |
|
294 | help : 'insert cell below', | |
280 |
help_index : ' |
|
295 | help_index : 'ed', | |
281 | handler : function (event) { |
|
296 | handler : function (event) { | |
282 |
|
|
297 | that.notebook.insert_cell_below(); | |
283 | return false; |
|
298 | that.notebook.select_next(); | |
284 | } |
|
299 | that.notebook.focus_cell(); | |
285 | }, |
|
300 | return false; | |
286 |
|
|
301 | } | |
287 | help : 'to markdown', |
|
302 | }, | |
288 |
|
|
303 | 'y' : { | |
289 | handler : function (event) { |
|
304 | help : 'to code', | |
290 | IPython.notebook.to_markdown(); |
|
305 | help_index : 'ca', | |
291 | return false; |
|
306 | handler : function (event) { | |
292 | } |
|
307 | that.notebook.to_code(); | |
293 | }, |
|
308 | return false; | |
294 |
|
|
309 | } | |
295 | help : 'to raw', |
|
310 | }, | |
296 |
|
|
311 | 'm' : { | |
297 | handler : function (event) { |
|
312 | help : 'to markdown', | |
298 | IPython.notebook.to_raw(); |
|
313 | help_index : 'cb', | |
299 | return false; |
|
314 | handler : function (event) { | |
300 | } |
|
315 | that.notebook.to_markdown(); | |
301 | }, |
|
316 | return false; | |
302 |
|
|
317 | } | |
303 | help : 'to heading 1', |
|
318 | }, | |
304 |
|
|
319 | 'r' : { | |
305 | handler : function (event) { |
|
320 | help : 'to raw', | |
306 | IPython.notebook.to_heading(undefined, 1); |
|
321 | help_index : 'cc', | |
307 | return false; |
|
322 | handler : function (event) { | |
308 | } |
|
323 | that.notebook.to_raw(); | |
309 | }, |
|
324 | return false; | |
310 |
|
|
325 | } | |
311 | help : 'to heading 2', |
|
326 | }, | |
312 |
|
|
327 | '1' : { | |
313 | handler : function (event) { |
|
328 | help : 'to heading 1', | |
314 | IPython.notebook.to_heading(undefined, 2); |
|
329 | help_index : 'cd', | |
315 | return false; |
|
330 | handler : function (event) { | |
316 | } |
|
331 | that.notebook.to_heading(undefined, 1); | |
317 | }, |
|
332 | return false; | |
318 |
|
|
333 | } | |
319 | help : 'to heading 3', |
|
334 | }, | |
320 |
|
|
335 | '2' : { | |
321 | handler : function (event) { |
|
336 | help : 'to heading 2', | |
322 | IPython.notebook.to_heading(undefined, 3); |
|
337 | help_index : 'ce', | |
323 | return false; |
|
338 | handler : function (event) { | |
324 | } |
|
339 | that.notebook.to_heading(undefined, 2); | |
325 | }, |
|
340 | return false; | |
326 |
|
|
341 | } | |
327 | help : 'to heading 4', |
|
342 | }, | |
328 |
|
|
343 | '3' : { | |
329 | handler : function (event) { |
|
344 | help : 'to heading 3', | |
330 | IPython.notebook.to_heading(undefined, 4); |
|
345 | help_index : 'cf', | |
331 | return false; |
|
346 | handler : function (event) { | |
332 | } |
|
347 | that.notebook.to_heading(undefined, 3); | |
333 | }, |
|
348 | return false; | |
334 |
|
|
349 | } | |
335 | help : 'to heading 5', |
|
350 | }, | |
336 |
|
|
351 | '4' : { | |
337 | handler : function (event) { |
|
352 | help : 'to heading 4', | |
338 | IPython.notebook.to_heading(undefined, 5); |
|
353 | help_index : 'cg', | |
339 | return false; |
|
354 | handler : function (event) { | |
340 | } |
|
355 | that.notebook.to_heading(undefined, 4); | |
341 | }, |
|
356 | return false; | |
342 |
|
|
357 | } | |
343 | help : 'to heading 6', |
|
358 | }, | |
344 |
|
|
359 | '5' : { | |
345 | handler : function (event) { |
|
360 | help : 'to heading 5', | |
346 | IPython.notebook.to_heading(undefined, 6); |
|
361 | help_index : 'ch', | |
347 | return false; |
|
362 | handler : function (event) { | |
348 | } |
|
363 | that.notebook.to_heading(undefined, 5); | |
349 | }, |
|
364 | return false; | |
350 |
|
|
365 | } | |
351 | help : 'toggle output', |
|
366 | }, | |
352 |
|
|
367 | '6' : { | |
353 | handler : function (event) { |
|
368 | help : 'to heading 6', | |
354 | IPython.notebook.toggle_output(); |
|
369 | help_index : 'ci', | |
355 | return false; |
|
370 | handler : function (event) { | |
356 | } |
|
371 | that.notebook.to_heading(undefined, 6); | |
357 | }, |
|
372 | return false; | |
358 | 'shift-o' : { |
|
373 | } | |
359 | help : 'toggle output scrolling', |
|
374 | }, | |
360 |
|
|
375 | 'o' : { | |
361 | handler : function (event) { |
|
376 | help : 'toggle output', | |
362 | IPython.notebook.toggle_output_scroll(); |
|
377 | help_index : 'gb', | |
363 | return false; |
|
378 | handler : function (event) { | |
364 | } |
|
379 | that.notebook.toggle_output(); | |
365 | }, |
|
380 | return false; | |
366 |
|
|
381 | } | |
367 | help : 'save notebook', |
|
382 | }, | |
368 |
|
|
383 | 'shift-o' : { | |
369 | handler : function (event) { |
|
384 | help : 'toggle output scrolling', | |
370 | IPython.notebook.save_checkpoint(); |
|
385 | help_index : 'gc', | |
371 | return false; |
|
386 | handler : function (event) { | |
372 | } |
|
387 | that.notebook.toggle_output_scroll(); | |
373 | }, |
|
388 | return false; | |
374 | 'ctrl-j' : { |
|
389 | } | |
375 | help : 'move cell down', |
|
390 | }, | |
376 |
|
|
391 | 's' : { | |
377 | handler : function (event) { |
|
392 | help : 'save notebook', | |
378 | IPython.notebook.move_cell_down(); |
|
393 | help_index : 'fa', | |
379 | return false; |
|
394 | handler : function (event) { | |
380 | } |
|
395 | that.notebook.save_checkpoint(); | |
381 | }, |
|
396 | return false; | |
382 | 'ctrl-k' : { |
|
397 | } | |
383 | help : 'move cell up', |
|
398 | }, | |
384 | help_index : 'ea', |
|
399 | 'ctrl-j' : { | |
385 | handler : function (event) { |
|
400 | help : 'move cell down', | |
386 | IPython.notebook.move_cell_up(); |
|
401 | help_index : 'eb', | |
387 | return false; |
|
402 | handler : function (event) { | |
388 | } |
|
403 | that.notebook.move_cell_down(); | |
389 | }, |
|
404 | return false; | |
390 |
|
|
405 | } | |
391 | help : 'toggle line numbers', |
|
406 | }, | |
392 | help_index : 'ga', |
|
407 | 'ctrl-k' : { | |
393 | handler : function (event) { |
|
408 | help : 'move cell up', | |
394 | IPython.notebook.cell_toggle_line_numbers(); |
|
409 | help_index : 'ea', | |
395 | return false; |
|
410 | handler : function (event) { | |
396 | } |
|
411 | that.notebook.move_cell_up(); | |
397 | }, |
|
412 | return false; | |
398 |
|
|
413 | } | |
399 | help : 'interrupt kernel (press twice)', |
|
414 | }, | |
400 |
|
|
415 | 'l' : { | |
401 | count: 2, |
|
416 | help : 'toggle line numbers', | |
402 | handler : function (event) { |
|
417 | help_index : 'ga', | |
403 | IPython.notebook.kernel.interrupt(); |
|
418 | handler : function (event) { | |
404 | return false; |
|
419 | that.notebook.cell_toggle_line_numbers(); | |
405 | } |
|
420 | return false; | |
406 |
} |
|
421 | } | |
407 |
|
|
422 | }, | |
408 | help : 'restart kernel (press twice)', |
|
423 | 'i' : { | |
409 | help_index : 'hb', |
|
424 | help : 'interrupt kernel (press twice)', | |
410 | count: 2, |
|
425 | help_index : 'ha', | |
411 | handler : function (event) { |
|
426 | count: 2, | |
412 | IPython.notebook.restart_kernel(); |
|
427 | handler : function (event) { | |
413 | return false; |
|
428 | that.notebook.kernel.interrupt(); | |
414 | } |
|
429 | return false; | |
415 |
} |
|
430 | } | |
416 |
|
|
431 | }, | |
417 | help : 'keyboard shortcuts', |
|
432 | '0' : { | |
418 | help_index : 'ge', |
|
433 | help : 'restart kernel (press twice)', | |
419 | handler : function (event) { |
|
434 | help_index : 'hb', | |
420 | IPython.quick_help.show_keyboard_shortcuts(); |
|
435 | count: 2, | |
421 | return false; |
|
436 | handler : function (event) { | |
422 | } |
|
437 | that.notebook.restart_kernel(); | |
423 | }, |
|
438 | return false; | |
424 |
|
|
439 | } | |
425 | help : 'undo last delete', |
|
440 | }, | |
426 |
|
|
441 | 'h' : { | |
427 | handler : function (event) { |
|
442 | help : 'keyboard shortcuts', | |
428 | IPython.notebook.undelete_cell(); |
|
443 | help_index : 'ge', | |
429 | return false; |
|
444 | handler : function (event) { | |
430 | } |
|
445 | that.quick_help.show_keyboard_shortcuts(); | |
431 | }, |
|
446 | return false; | |
432 | 'shift-m' : { |
|
447 | } | |
433 | help : 'merge cell below', |
|
448 | }, | |
434 |
|
|
449 | 'z' : { | |
435 | handler : function (event) { |
|
450 | help : 'undo last delete', | |
436 | IPython.notebook.merge_cell_below(); |
|
451 | help_index : 'ei', | |
437 | return false; |
|
452 | handler : function (event) { | |
438 | } |
|
453 | that.notebook.undelete_cell(); | |
439 | }, |
|
454 | return false; | |
440 |
|
|
455 | } | |
441 | help : 'close pager', |
|
456 | }, | |
442 |
|
|
457 | 'shift-m' : { | |
443 | handler : function (event) { |
|
458 | help : 'merge cell below', | |
444 | IPython.pager.collapse(); |
|
459 | help_index : 'ek', | |
445 | return false; |
|
460 | handler : function (event) { | |
446 | } |
|
461 | that.notebook.merge_cell_below(); | |
447 | }, |
|
462 | return false; | |
448 | }; |
|
463 | } | |
449 |
|
464 | }, | ||
450 |
|
465 | 'q' : { | ||
451 | // Main keyboard manager for the notebook |
|
466 | help : 'close pager', | |
452 |
|
467 | help_index : 'gd', | ||
453 | var ShortcutManager = IPython.keyboard.ShortcutManager; |
|
468 | handler : function (event) { | |
454 | var keycodes = IPython.keyboard.keycodes; |
|
469 | that.pager.collapse(); | |
455 |
|
470 | return false; | ||
456 | var KeyboardManager = function () { |
|
471 | } | |
457 | this.mode = 'command'; |
|
472 | }, | |
458 | this.enabled = true; |
|
473 | }; | |
459 | this.bind_events(); |
|
|||
460 | this.command_shortcuts = new ShortcutManager(); |
|
|||
461 | this.command_shortcuts.add_shortcuts(default_common_shortcuts); |
|
|||
462 | this.command_shortcuts.add_shortcuts(default_command_shortcuts); |
|
|||
463 | this.edit_shortcuts = new ShortcutManager(); |
|
|||
464 | this.edit_shortcuts.add_shortcuts(default_common_shortcuts); |
|
|||
465 | this.edit_shortcuts.add_shortcuts(default_edit_shortcuts); |
|
|||
466 | }; |
|
474 | }; | |
467 |
|
475 | |||
468 | KeyboardManager.prototype.bind_events = function () { |
|
476 | KeyboardManager.prototype.bind_events = function () { | |
@@ -473,7 +481,7 b' var IPython = (function (IPython) {' | |||||
473 | }; |
|
481 | }; | |
474 |
|
482 | |||
475 | KeyboardManager.prototype.handle_keydown = function (event) { |
|
483 | KeyboardManager.prototype.handle_keydown = function (event) { | |
476 |
var notebook = |
|
484 | var notebook = this.notebook; | |
477 |
|
485 | |||
478 | if (event.which === keycodes.esc) { |
|
486 | if (event.which === keycodes.esc) { | |
479 | // Intercept escape at highest level to avoid closing |
|
487 | // Intercept escape at highest level to avoid closing | |
@@ -545,18 +553,14 b' var IPython = (function (IPython) {' | |||||
545 | // is_focused must be used to check for the case where an element within |
|
553 | // is_focused must be used to check for the case where an element within | |
546 | // the element being removed is focused. |
|
554 | // the element being removed is focused. | |
547 | e.on('remove', function () { |
|
555 | e.on('remove', function () { | |
548 |
if ( |
|
556 | if (utils.is_focused(e[0])) { | |
549 | that.enable(); |
|
557 | that.enable(); | |
550 | } |
|
558 | } | |
551 | }); |
|
559 | }); | |
552 | }; |
|
560 | }; | |
553 |
|
561 | |||
554 |
|
562 | // For backwards compatability. | ||
555 | IPython.default_common_shortcuts = default_common_shortcuts; |
|
|||
556 | IPython.default_edit_shortcuts = default_edit_shortcuts; |
|
|||
557 | IPython.default_command_shortcuts = default_command_shortcuts; |
|
|||
558 | IPython.KeyboardManager = KeyboardManager; |
|
563 | IPython.KeyboardManager = KeyboardManager; | |
559 |
|
564 | |||
560 | return IPython; |
|
565 | return {'KeyboardManager': KeyboardManager}; | |
561 |
|
566 | }); | ||
562 | }(IPython)); |
|
@@ -1,19 +1,15 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
3 | ||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
4 | define([ | |
5 | // the file COPYING, distributed as part of this software. |
|
5 | 'base/js/namespace', | |
6 | //---------------------------------------------------------------------------- |
|
6 | 'jquery', | |
7 |
|
7 | ], function(IPython, $) { | ||
8 | //============================================================================ |
|
|||
9 | // Layout |
|
|||
10 | //============================================================================ |
|
|||
11 |
|
||||
12 | var IPython = (function (IPython) { |
|
|||
13 | "use strict"; |
|
8 | "use strict"; | |
14 |
|
9 | |||
15 | var LayoutManager = function () { |
|
10 | var LayoutManager = function () { | |
16 | this.bind_events(); |
|
11 | this.bind_events(); | |
|
12 | this.pager = undefined; | |||
17 | }; |
|
13 | }; | |
18 |
|
14 | |||
19 | LayoutManager.prototype.bind_events = function () { |
|
15 | LayoutManager.prototype.bind_events = function () { | |
@@ -43,19 +39,20 b' var IPython = (function (IPython) {' | |||||
43 | var app_height = this.app_height(); // content height |
|
39 | var app_height = this.app_height(); // content height | |
44 |
|
40 | |||
45 | $('#ipython-main-app').height(app_height); // content+padding+border height |
|
41 | $('#ipython-main-app').height(app_height); // content+padding+border height | |
46 |
|
42 | if (this.pager) { | ||
47 |
var pager_height = |
|
43 | var pager_height = this.pager.percentage_height*app_height; | |
48 | var pager_splitter_height = $('div#pager_splitter').outerHeight(true); |
|
44 | var pager_splitter_height = $('div#pager_splitter').outerHeight(true); | |
49 | $('div#pager').outerHeight(pager_height); |
|
45 | $('div#pager').outerHeight(pager_height); | |
50 |
if ( |
|
46 | if (this.pager.expanded) { | |
51 | $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height); |
|
47 | $('div#notebook').outerHeight(app_height-pager_height-pager_splitter_height); | |
52 | } else { |
|
48 | } else { | |
53 | $('div#notebook').outerHeight(app_height-pager_splitter_height); |
|
49 | $('div#notebook').outerHeight(app_height-pager_splitter_height); | |
|
50 | } | |||
54 | } |
|
51 | } | |
55 | }; |
|
52 | }; | |
56 |
|
53 | |||
|
54 | // Backwards compatability. | |||
57 | IPython.LayoutManager = LayoutManager; |
|
55 | IPython.LayoutManager = LayoutManager; | |
58 |
|
56 | |||
59 | return IPython; |
|
57 | return {'LayoutManager': LayoutManager}; | |
60 |
|
58 | }); | ||
61 | }(IPython)); |
|
@@ -1,78 +1,96 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | require([ | |
9 | // On document ready |
|
5 | 'base/js/namespace', | |
10 | //============================================================================ |
|
6 | 'jquery', | |
11 |
|
7 | 'notebook/js/notebook', | ||
12 | // for the time beeing, we have to pass marked as a parameter here, |
|
8 | 'base/js/utils', | |
13 | // as injecting require.js make marked not to put itself in the globals, |
|
9 | 'base/js/page', | |
14 | // which make both this file fail at setting marked configuration, and textcell.js |
|
10 | 'notebook/js/layoutmanager', | |
15 | // which search marked into global. |
|
11 | 'base/js/events', | |
16 | require(['components/marked/lib/marked', |
|
12 | 'auth/js/loginwidget', | |
17 | 'widgets/js/init', |
|
13 | 'notebook/js/maintoolbar', | |
18 | 'components/bootstrap-tour/build/js/bootstrap-tour.min'], |
|
14 | 'notebook/js/pager', | |
19 |
|
15 | 'notebook/js/quickhelp', | ||
20 | function (marked) { |
|
16 | 'notebook/js/menubar', | |
|
17 | 'notebook/js/notificationarea', | |||
|
18 | 'notebook/js/savewidget', | |||
|
19 | 'notebook/js/keyboardmanager', | |||
|
20 | 'notebook/js/config', | |||
|
21 | 'notebook/js/kernelselector', | |||
|
22 | // only loaded, not used: | |||
|
23 | 'custom/custom', | |||
|
24 | ], function( | |||
|
25 | IPython, | |||
|
26 | $, | |||
|
27 | notebook, | |||
|
28 | utils, | |||
|
29 | page, | |||
|
30 | layoutmanager, | |||
|
31 | events, | |||
|
32 | loginwidget, | |||
|
33 | maintoolbar, | |||
|
34 | pager, | |||
|
35 | quickhelp, | |||
|
36 | menubar, | |||
|
37 | notificationarea, | |||
|
38 | savewidget, | |||
|
39 | keyboardmanager, | |||
|
40 | config, | |||
|
41 | kernelselector | |||
|
42 | ) { | |||
21 | "use strict"; |
|
43 | "use strict"; | |
22 |
|
44 | |||
23 | window.marked = marked; |
|
45 | var common_options = { | |
24 |
|
46 | base_url : utils.get_body_data("baseUrl"), | ||
25 | // monkey patch CM to be able to syntax highlight cell magics |
|
47 | ws_url : IPython.utils.get_body_data("wsUrl"), | |
26 | // bug reported upstream, |
|
48 | notebook_path : utils.get_body_data("notebookPath"), | |
27 | // see https://github.com/marijnh/CodeMirror2/issues/670 |
|
49 | notebook_name : utils.get_body_data('notebookName') | |
28 | if(CodeMirror.getMode(1,'text/plain').indent === undefined ){ |
|
|||
29 | console.log('patching CM for undefined indent'); |
|
|||
30 | CodeMirror.modes.null = function() { |
|
|||
31 | return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}}; |
|
|||
32 | }; |
|
|||
33 | } |
|
|||
34 |
|
||||
35 | CodeMirror.patchedGetMode = function(config, mode){ |
|
|||
36 | var cmmode = CodeMirror.getMode(config, mode); |
|
|||
37 | if(cmmode.indent === null) { |
|
|||
38 | console.log('patch mode "' , mode, '" on the fly'); |
|
|||
39 | cmmode.indent = function(){return 0;}; |
|
|||
40 | } |
|
|||
41 | return cmmode; |
|
|||
42 | }; |
|
|||
43 | // end monkey patching CodeMirror |
|
|||
44 |
|
||||
45 | IPython.mathjaxutils.init(); |
|
|||
46 |
|
||||
47 | $('#ipython-main-app').addClass('border-box-sizing'); |
|
|||
48 | $('div#notebook_panel').addClass('border-box-sizing'); |
|
|||
49 |
|
||||
50 | var opts = { |
|
|||
51 | base_url : IPython.utils.get_body_data("baseUrl"), |
|
|||
52 | notebook_path : IPython.utils.get_body_data("notebookPath"), |
|
|||
53 | notebook_name : IPython.utils.get_body_data('notebookName') |
|
|||
54 | }; |
|
50 | }; | |
55 |
|
51 | |||
56 | IPython.page = new IPython.Page(); |
|
52 | var user_config = $.extend({}, config.default_config); | |
57 | IPython.layout_manager = new IPython.LayoutManager(); |
|
53 | var page = new page.Page(); | |
58 | IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter'); |
|
54 | var layout_manager = new layoutmanager.LayoutManager(); | |
59 | IPython.quick_help = new IPython.QuickHelp(); |
|
55 | var pager = new pager.Pager('div#pager', 'div#pager_splitter', { | |
60 | try { |
|
56 | layout_manager: layout_manager, | |
61 | IPython.tour = new IPython.NotebookTour(); |
|
57 | events: events}); | |
62 | } catch (e) { |
|
58 | var keyboard_manager = new keyboardmanager.KeyboardManager({ | |
63 | console.log("Failed to instantiate Notebook Tour", e); |
|
59 | pager: pager, | |
64 | } |
|
60 | events: events}); | |
65 | IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts); |
|
61 | var save_widget = new savewidget.SaveWidget('span#save_widget', { | |
66 | IPython.notebook = new IPython.Notebook('div#notebook', opts); |
|
62 | events: events, | |
67 |
|
|
63 | keyboard_manager: keyboard_manager}); | |
68 | IPython.save_widget = new IPython.SaveWidget('span#save_widget'); |
|
64 | var notebook = new notebook.Notebook('div#notebook', $.extend({ | |
69 | IPython.menubar = new IPython.MenuBar('#menubar', opts); |
|
65 | events: events, | |
70 | IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container'); |
|
66 | keyboard_manager: keyboard_manager, | |
71 | IPython.tooltip = new IPython.Tooltip(); |
|
67 | save_widget: save_widget, | |
72 | IPython.notification_area = new IPython.NotificationArea('#notification_area'); |
|
68 | config: user_config}, | |
73 | IPython.notification_area.init_notification_widgets(); |
|
69 | common_options)); | |
74 |
|
70 | var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options); | ||
75 | IPython.layout_manager.do_resize(); |
|
71 | var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', { | |
|
72 | notebook: notebook, | |||
|
73 | events: events}); | |||
|
74 | var quick_help = new quickhelp.QuickHelp({ | |||
|
75 | keyboard_manager: keyboard_manager, | |||
|
76 | events: events, | |||
|
77 | notebook: notebook}); | |||
|
78 | var menubar = new menubar.MenuBar('#menubar', $.extend({ | |||
|
79 | notebook: notebook, | |||
|
80 | layout_manager: layout_manager, | |||
|
81 | events: events, | |||
|
82 | save_widget: save_widget, | |||
|
83 | quick_help: quick_help}, | |||
|
84 | common_options)); | |||
|
85 | var notification_area = new notificationarea.NotificationArea( | |||
|
86 | '#notification_area', { | |||
|
87 | events: events, | |||
|
88 | save_widget: save_widget, | |||
|
89 | notebook: notebook, | |||
|
90 | keyboard_manager: keyboard_manager}); | |||
|
91 | notification_area.init_notification_widgets(); | |||
|
92 | var kernel_selector = new kernelselector.KernelSelector( | |||
|
93 | '#kernel_selector_widget', notebook); | |||
76 |
|
94 | |||
77 | $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+ |
|
95 | $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+ | |
78 | '<span id="test2" style="font-weight: bold;">x</span>'+ |
|
96 | '<span id="test2" style="font-weight: bold;">x</span>'+ | |
@@ -85,43 +103,37 b' function (marked) {' | |||||
85 | } |
|
103 | } | |
86 | $('#fonttest').remove(); |
|
104 | $('#fonttest').remove(); | |
87 |
|
105 | |||
88 |
|
|
106 | page.show(); | |
89 |
|
107 | |||
90 |
|
|
108 | layout_manager.do_resize(); | |
91 | var first_load = function () { |
|
109 | var first_load = function () { | |
92 |
|
|
110 | layout_manager.do_resize(); | |
93 | var hash = document.location.hash; |
|
111 | var hash = document.location.hash; | |
94 | if (hash) { |
|
112 | if (hash) { | |
95 | document.location.hash = ''; |
|
113 | document.location.hash = ''; | |
96 | document.location.hash = hash; |
|
114 | document.location.hash = hash; | |
97 | } |
|
115 | } | |
98 |
|
|
116 | notebook.set_autosave_interval(notebook.minimum_autosave_interval); | |
99 | // only do this once |
|
117 | // only do this once | |
100 |
|
|
118 | events.off('notebook_loaded.Notebook', first_load); | |
101 | }; |
|
119 | }; | |
|
120 | events.on('notebook_loaded.Notebook', first_load); | |||
102 |
|
121 | |||
103 | $([IPython.events]).on('notebook_loaded.Notebook', first_load); |
|
122 | IPython.page = page; | |
104 | $([IPython.events]).trigger('app_initialized.NotebookApp'); |
|
123 | IPython.layout_manager = layout_manager; | |
105 | IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path); |
|
124 | IPython.notebook = notebook; | |
|
125 | IPython.pager = pager; | |||
|
126 | IPython.quick_help = quick_help; | |||
|
127 | IPython.login_widget = login_widget; | |||
|
128 | IPython.menubar = menubar; | |||
|
129 | IPython.toolbar = toolbar; | |||
|
130 | IPython.notification_area = notification_area; | |||
|
131 | IPython.keyboard_manager = keyboard_manager; | |||
|
132 | IPython.save_widget = save_widget; | |||
|
133 | IPython.config = user_config; | |||
|
134 | IPython.tooltip = notebook.tooltip; | |||
|
135 | ||||
|
136 | events.trigger('app_initialized.NotebookApp'); | |||
|
137 | notebook.load_notebook(common_options.notebook_name, common_options.notebook_path); | |||
106 |
|
138 | |||
107 | if (marked) { |
|
|||
108 | marked.setOptions({ |
|
|||
109 | gfm : true, |
|
|||
110 | tables: true, |
|
|||
111 | langPrefix: "language-", |
|
|||
112 | highlight: function(code, lang) { |
|
|||
113 | if (!lang) { |
|
|||
114 | // no language, no highlight |
|
|||
115 | return code; |
|
|||
116 | } |
|
|||
117 | var highlighted; |
|
|||
118 | try { |
|
|||
119 | highlighted = hljs.highlight(lang, code, false); |
|
|||
120 | } catch(err) { |
|
|||
121 | highlighted = hljs.highlightAuto(code); |
|
|||
122 | } |
|
|||
123 | return highlighted.value; |
|
|||
124 | } |
|
|||
125 | }); |
|
|||
126 | } |
|
|||
127 | }); |
|
139 | }); |
@@ -1,35 +1,43 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
3 | ||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
4 | define([ | |
5 | // the file COPYING, distributed as part of this software. |
|
5 | 'base/js/namespace', | |
6 | //---------------------------------------------------------------------------- |
|
6 | 'jquery', | |
7 |
|
7 | 'notebook/js/toolbar', | ||
8 | //============================================================================ |
|
8 | 'notebook/js/celltoolbar', | |
9 | // ToolBar |
|
9 | ], function(IPython, $, toolbar, celltoolbar) { | |
10 | //============================================================================ |
|
|||
11 |
|
||||
12 | var IPython = (function (IPython) { |
|
|||
13 | "use strict"; |
|
10 | "use strict"; | |
14 |
|
11 | |||
15 | var MainToolBar = function (selector) { |
|
12 | var MainToolBar = function (selector, options) { | |
16 | IPython.ToolBar.apply(this, arguments); |
|
13 | // Constructor | |
|
14 | // | |||
|
15 | // Parameters: | |||
|
16 | // selector: string | |||
|
17 | // options: dictionary | |||
|
18 | // Dictionary of keyword arguments. | |||
|
19 | // events: $(Events) instance | |||
|
20 | // notebook: Notebook instance | |||
|
21 | toolbar.ToolBar.apply(this, arguments); | |||
|
22 | this.events = options.events; | |||
|
23 | this.notebook = options.notebook; | |||
17 | this.construct(); |
|
24 | this.construct(); | |
18 | this.add_celltype_list(); |
|
25 | this.add_celltype_list(); | |
19 | this.add_celltoolbar_list(); |
|
26 | this.add_celltoolbar_list(); | |
20 | this.bind_events(); |
|
27 | this.bind_events(); | |
21 | }; |
|
28 | }; | |
22 |
|
29 | |||
23 |
MainToolBar.prototype = new |
|
30 | MainToolBar.prototype = new toolbar.ToolBar(); | |
24 |
|
31 | |||
25 | MainToolBar.prototype.construct = function () { |
|
32 | MainToolBar.prototype.construct = function () { | |
|
33 | var that = this; | |||
26 | this.add_buttons_group([ |
|
34 | this.add_buttons_group([ | |
27 | { |
|
35 | { | |
28 | id : 'save_b', |
|
36 | id : 'save_b', | |
29 | label : 'Save and Checkpoint', |
|
37 | label : 'Save and Checkpoint', | |
30 |
icon : ' |
|
38 | icon : 'fa-save', | |
31 | callback : function () { |
|
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 | id : 'insert_below_b', |
|
47 | id : 'insert_below_b', | |
40 | label : 'Insert Cell Below', |
|
48 | label : 'Insert Cell Below', | |
41 |
icon : ' |
|
49 | icon : 'fa-plus', | |
42 | callback : function () { |
|
50 | callback : function () { | |
43 |
|
|
51 | that.notebook.insert_cell_below('code'); | |
44 |
|
|
52 | that.notebook.select_next(); | |
45 |
|
|
53 | that.notebook.focus_cell(); | |
46 | } |
|
54 | } | |
47 | } |
|
55 | } | |
48 | ],'insert_above_below'); |
|
56 | ],'insert_above_below'); | |
@@ -51,25 +59,25 b' var IPython = (function (IPython) {' | |||||
51 | { |
|
59 | { | |
52 | id : 'cut_b', |
|
60 | id : 'cut_b', | |
53 | label : 'Cut Cell', |
|
61 | label : 'Cut Cell', | |
54 |
icon : ' |
|
62 | icon : 'fa-cut', | |
55 | callback : function () { |
|
63 | callback : function () { | |
56 |
|
|
64 | that.notebook.cut_cell(); | |
57 | } |
|
65 | } | |
58 | }, |
|
66 | }, | |
59 | { |
|
67 | { | |
60 | id : 'copy_b', |
|
68 | id : 'copy_b', | |
61 | label : 'Copy Cell', |
|
69 | label : 'Copy Cell', | |
62 |
icon : ' |
|
70 | icon : 'fa-copy', | |
63 | callback : function () { |
|
71 | callback : function () { | |
64 |
|
|
72 | that.notebook.copy_cell(); | |
65 | } |
|
73 | } | |
66 | }, |
|
74 | }, | |
67 | { |
|
75 | { | |
68 | id : 'paste_b', |
|
76 | id : 'paste_b', | |
69 | label : 'Paste Cell Below', |
|
77 | label : 'Paste Cell Below', | |
70 |
icon : ' |
|
78 | icon : 'fa-paste', | |
71 | callback : function () { |
|
79 | callback : function () { | |
72 |
|
|
80 | that.notebook.paste_cell_below(); | |
73 | } |
|
81 | } | |
74 | } |
|
82 | } | |
75 | ],'cut_copy_paste'); |
|
83 | ],'cut_copy_paste'); | |
@@ -78,17 +86,17 b' var IPython = (function (IPython) {' | |||||
78 | { |
|
86 | { | |
79 | id : 'move_up_b', |
|
87 | id : 'move_up_b', | |
80 | label : 'Move Cell Up', |
|
88 | label : 'Move Cell Up', | |
81 |
icon : ' |
|
89 | icon : 'fa-arrow-up', | |
82 | callback : function () { |
|
90 | callback : function () { | |
83 |
|
|
91 | that.notebook.move_cell_up(); | |
84 | } |
|
92 | } | |
85 | }, |
|
93 | }, | |
86 | { |
|
94 | { | |
87 | id : 'move_down_b', |
|
95 | id : 'move_down_b', | |
88 | label : 'Move Cell Down', |
|
96 | label : 'Move Cell Down', | |
89 |
icon : ' |
|
97 | icon : 'fa-arrow-down', | |
90 | callback : function () { |
|
98 | callback : function () { | |
91 |
|
|
99 | that.notebook.move_cell_down(); | |
92 | } |
|
100 | } | |
93 | } |
|
101 | } | |
94 | ],'move_up_down'); |
|
102 | ],'move_up_down'); | |
@@ -98,26 +106,26 b' var IPython = (function (IPython) {' | |||||
98 | { |
|
106 | { | |
99 | id : 'run_b', |
|
107 | id : 'run_b', | |
100 | label : 'Run Cell', |
|
108 | label : 'Run Cell', | |
101 |
icon : ' |
|
109 | icon : 'fa-play', | |
102 | callback : function () { |
|
110 | callback : function () { | |
103 | // emulate default shift-enter behavior |
|
111 | // emulate default shift-enter behavior | |
104 |
|
|
112 | that.notebook.execute_cell_and_select_below(); | |
105 | } |
|
113 | } | |
106 | }, |
|
114 | }, | |
107 | { |
|
115 | { | |
108 | id : 'interrupt_b', |
|
116 | id : 'interrupt_b', | |
109 | label : 'Interrupt', |
|
117 | label : 'Interrupt', | |
110 |
icon : ' |
|
118 | icon : 'fa-stop', | |
111 | callback : function () { |
|
119 | callback : function () { | |
112 |
|
|
120 | that.notebook.session.interrupt_kernel(); | |
113 | } |
|
121 | } | |
114 | }, |
|
122 | }, | |
115 | { |
|
123 | { | |
116 | id : 'repeat_b', |
|
124 | id : 'repeat_b', | |
117 | label : 'Restart Kernel', |
|
125 | label : 'Restart Kernel', | |
118 |
icon : ' |
|
126 | icon : 'fa-repeat', | |
119 | callback : function () { |
|
127 | callback : function () { | |
120 |
|
|
128 | that.notebook.restart_kernel(); | |
121 | } |
|
129 | } | |
122 | } |
|
130 | } | |
123 | ],'run_int'); |
|
131 | ],'run_int'); | |
@@ -150,30 +158,31 b' var IPython = (function (IPython) {' | |||||
150 | .addClass('form-control select-xs') |
|
158 | .addClass('form-control select-xs') | |
151 | .append($('<option/>').attr('value', '').text('None')); |
|
159 | .append($('<option/>').attr('value', '').text('None')); | |
152 | this.element.append(label).append(select); |
|
160 | this.element.append(label).append(select); | |
|
161 | var that = this; | |||
153 | select.change(function() { |
|
162 | select.change(function() { | |
154 | var val = $(this).val() |
|
163 | var val = $(this).val(); | |
155 | if (val =='') { |
|
164 | if (val ==='') { | |
156 |
|
|
165 | celltoolbar.CellToolbar.global_hide(); | |
157 |
delete |
|
166 | delete that.notebook.metadata.celltoolbar; | |
158 | } else { |
|
167 | } else { | |
159 |
|
|
168 | celltoolbar.CellToolbar.global_show(); | |
160 |
|
|
169 | celltoolbar.CellToolbar.activate_preset(val, that.events); | |
161 |
|
|
170 | that.notebook.metadata.celltoolbar = val; | |
162 | } |
|
171 | } | |
163 | }); |
|
172 | }); | |
164 | // Setup the currently registered presets. |
|
173 | // Setup the currently registered presets. | |
165 |
var presets = |
|
174 | var presets = celltoolbar.CellToolbar.list_presets(); | |
166 | for (var i=0; i<presets.length; i++) { |
|
175 | for (var i=0; i<presets.length; i++) { | |
167 | var name = presets[i]; |
|
176 | var name = presets[i]; | |
168 | select.append($('<option/>').attr('value', name).text(name)); |
|
177 | select.append($('<option/>').attr('value', name).text(name)); | |
169 | } |
|
178 | } | |
170 | // Setup future preset registrations. |
|
179 | // Setup future preset registrations. | |
171 |
|
|
180 | this.events.on('preset_added.CellToolbar', function (event, data) { | |
172 | var name = data.name; |
|
181 | var name = data.name; | |
173 | select.append($('<option/>').attr('value', name).text(name)); |
|
182 | select.append($('<option/>').attr('value', name).text(name)); | |
174 | }); |
|
183 | }); | |
175 | // Update select value when a preset is activated. |
|
184 | // Update select value when a preset is activated. | |
176 |
|
|
185 | this.events.on('preset_activated.CellToolbar', function (event, data) { | |
177 | if (select.val() !== data.name) |
|
186 | if (select.val() !== data.name) | |
178 | select.val(data.name); |
|
187 | select.val(data.name); | |
179 | }); |
|
188 | }); | |
@@ -186,26 +195,26 b' var IPython = (function (IPython) {' | |||||
186 | this.element.find('#cell_type').change(function () { |
|
195 | this.element.find('#cell_type').change(function () { | |
187 | var cell_type = $(this).val(); |
|
196 | var cell_type = $(this).val(); | |
188 | if (cell_type === 'code') { |
|
197 | if (cell_type === 'code') { | |
189 |
|
|
198 | that.notebook.to_code(); | |
190 | } else if (cell_type === 'markdown') { |
|
199 | } else if (cell_type === 'markdown') { | |
191 |
|
|
200 | that.notebook.to_markdown(); | |
192 | } else if (cell_type === 'raw') { |
|
201 | } else if (cell_type === 'raw') { | |
193 |
|
|
202 | that.notebook.to_raw(); | |
194 | } else if (cell_type === 'heading1') { |
|
203 | } else if (cell_type === 'heading1') { | |
195 |
|
|
204 | that.notebook.to_heading(undefined, 1); | |
196 | } else if (cell_type === 'heading2') { |
|
205 | } else if (cell_type === 'heading2') { | |
197 |
|
|
206 | that.notebook.to_heading(undefined, 2); | |
198 | } else if (cell_type === 'heading3') { |
|
207 | } else if (cell_type === 'heading3') { | |
199 |
|
|
208 | that.notebook.to_heading(undefined, 3); | |
200 | } else if (cell_type === 'heading4') { |
|
209 | } else if (cell_type === 'heading4') { | |
201 |
|
|
210 | that.notebook.to_heading(undefined, 4); | |
202 | } else if (cell_type === 'heading5') { |
|
211 | } else if (cell_type === 'heading5') { | |
203 |
|
|
212 | that.notebook.to_heading(undefined, 5); | |
204 | } else if (cell_type === 'heading6') { |
|
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 | if (data.cell_type === 'heading') { |
|
218 | if (data.cell_type === 'heading') { | |
210 | that.element.find('#cell_type').val(data.cell_type+data.level); |
|
219 | that.element.find('#cell_type').val(data.cell_type+data.level); | |
211 | } else { |
|
220 | } else { | |
@@ -214,8 +223,8 b' var IPython = (function (IPython) {' | |||||
214 | }); |
|
223 | }); | |
215 | }; |
|
224 | }; | |
216 |
|
225 | |||
|
226 | // Backwards compatability. | |||
217 | IPython.MainToolBar = MainToolBar; |
|
227 | IPython.MainToolBar = MainToolBar; | |
218 |
|
228 | |||
219 | return IPython; |
|
229 | return {'MainToolBar': MainToolBar}; | |
220 |
|
230 | }); | ||
221 | }(IPython)); |
|
@@ -1,18 +1,12 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2008-2012 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
3 | ||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
4 | define([ | |
5 | // the file COPYING, distributed as part of this software. |
|
5 | 'base/js/namespace', | |
6 | //---------------------------------------------------------------------------- |
|
6 | 'jquery', | |
7 |
|
7 | 'base/js/utils', | ||
8 | //============================================================================ |
|
8 | 'base/js/dialog', | |
9 | // MathJax utility functions |
|
9 | ], function(IPython, $, utils, dialog) { | |
10 | //============================================================================ |
|
|||
11 |
|
||||
12 |
|
||||
13 | IPython.namespace('IPython.mathjaxutils'); |
|
|||
14 |
|
||||
15 | IPython.mathjaxutils = (function (IPython) { |
|
|||
16 | "use strict"; |
|
10 | "use strict"; | |
17 |
|
11 | |||
18 | var init = function () { |
|
12 | var init = function () { | |
@@ -75,7 +69,7 b' IPython.mathjaxutils = (function (IPython) {' | |||||
75 | "which will prevent this dialog from appearing." |
|
69 | "which will prevent this dialog from appearing." | |
76 | ) |
|
70 | ) | |
77 | ); |
|
71 | ); | |
78 |
|
|
72 | dialog.modal({ | |
79 | title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'", |
|
73 | title : "Failed to retrieve MathJax from '" + window.mathjax_url + "'", | |
80 | body : message, |
|
74 | body : message, | |
81 | buttons : { |
|
75 | buttons : { | |
@@ -110,7 +104,7 b' IPython.mathjaxutils = (function (IPython) {' | |||||
110 | .replace(/</g, "<") // use HTML entity for < |
|
104 | .replace(/</g, "<") // use HTML entity for < | |
111 | .replace(/>/g, ">") // use HTML entity for > |
|
105 | .replace(/>/g, ">") // use HTML entity for > | |
112 | ; |
|
106 | ; | |
113 |
if ( |
|
107 | if (utils.browser === 'msie') { | |
114 | block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n"); |
|
108 | block = block.replace(/(%[^\n]*)\n/g, "$1<br/>\n"); | |
115 | } |
|
109 | } | |
116 | while (j > i) { |
|
110 | while (j > i) { | |
@@ -159,7 +153,7 b' IPython.mathjaxutils = (function (IPython) {' | |||||
159 | de_tilde = function (text) { return text; }; |
|
153 | de_tilde = function (text) { return text; }; | |
160 | } |
|
154 | } | |
161 |
|
155 | |||
162 |
var blocks = |
|
156 | var blocks = utils.regex_split(text.replace(/\r\n?/g, "\n"),MATHSPLIT); | |
163 |
|
157 | |||
164 | for (var i = 1, m = blocks.length; i < m; i += 2) { |
|
158 | for (var i = 1, m = blocks.length; i < m; i += 2) { | |
165 | var block = blocks[i]; |
|
159 | var block = blocks[i]; | |
@@ -242,10 +236,13 b' IPython.mathjaxutils = (function (IPython) {' | |||||
242 | return text; |
|
236 | return text; | |
243 | }; |
|
237 | }; | |
244 |
|
238 | |||
245 | return { |
|
239 | var mathjaxutils = { | |
246 | init : init, |
|
240 | init : init, | |
247 | remove_math : remove_math, |
|
241 | remove_math : remove_math, | |
248 | replace_math : replace_math |
|
242 | replace_math : replace_math | |
249 | }; |
|
243 | }; | |
250 |
|
244 | |||
251 | }(IPython)); |
|
245 | IPython.mathjaxutils = mathjaxutils; | |
|
246 | ||||
|
247 | return mathjaxutils; | |||
|
248 | }); |
@@ -1,40 +1,49 b'' | |||||
1 | // Copyright (c) IPython Development Team. |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Distributed under the terms of the Modified BSD License. |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 |
|
3 | |||
4 | //============================================================================ |
|
4 | define([ | |
5 | // MenuBar |
|
5 | 'base/js/namespace', | |
6 | //============================================================================ |
|
6 | 'jquery', | |
7 |
|
7 | 'base/js/utils', | ||
8 | /** |
|
8 | 'notebook/js/tour', | |
9 | * @module IPython |
|
9 | 'bootstrap', | |
10 | * @namespace IPython |
|
10 | 'moment', | |
11 | * @submodule MenuBar |
|
11 | ], function(IPython, $, utils, tour, bootstrap, moment) { | |
12 | */ |
|
|||
13 |
|
||||
14 |
|
||||
15 | var IPython = (function (IPython) { |
|
|||
16 | "use strict"; |
|
12 | "use strict"; | |
17 |
|
13 | |||
18 | var utils = IPython.utils; |
|
|||
19 |
|
||||
20 | /** |
|
|||
21 | * A MenuBar Class to generate the menubar of IPython notebook |
|
|||
22 | * @Class MenuBar |
|
|||
23 | * |
|
|||
24 | * @constructor |
|
|||
25 | * |
|
|||
26 | * |
|
|||
27 | * @param selector {string} selector for the menubar element in DOM |
|
|||
28 | * @param {object} [options] |
|
|||
29 | * @param [options.base_url] {String} String to use for the |
|
|||
30 | * base project url. Default is to inspect |
|
|||
31 | * $('body').data('baseUrl'); |
|
|||
32 | * does not support change for now is set through this option |
|
|||
33 | */ |
|
|||
34 | var MenuBar = function (selector, options) { |
|
14 | var MenuBar = function (selector, options) { | |
|
15 | // Constructor | |||
|
16 | // | |||
|
17 | // A MenuBar Class to generate the menubar of IPython notebook | |||
|
18 | // | |||
|
19 | // Parameters: | |||
|
20 | // selector: string | |||
|
21 | // options: dictionary | |||
|
22 | // Dictionary of keyword arguments. | |||
|
23 | // notebook: Notebook instance | |||
|
24 | // layout_manager: LayoutManager instance | |||
|
25 | // events: $(Events) instance | |||
|
26 | // save_widget: SaveWidget instance | |||
|
27 | // quick_help: QuickHelp instance | |||
|
28 | // base_url : string | |||
|
29 | // notebook_path : string | |||
|
30 | // notebook_name : string | |||
35 | options = options || {}; |
|
31 | options = options || {}; | |
36 |
this.base_url = options.base_url || |
|
32 | this.base_url = options.base_url || utils.get_body_data("baseUrl"); | |
37 | this.selector = selector; |
|
33 | this.selector = selector; | |
|
34 | this.notebook = options.notebook; | |||
|
35 | this.layout_manager = options.layout_manager; | |||
|
36 | this.events = options.events; | |||
|
37 | this.save_widget = options.save_widget; | |||
|
38 | this.quick_help = options.quick_help; | |||
|
39 | ||||
|
40 | try { | |||
|
41 | this.tour = new tour.Tour(this.notebook, this.events); | |||
|
42 | } catch (e) { | |||
|
43 | this.tour = undefined; | |||
|
44 | console.log("Failed to instantiate Notebook Tour", e); | |||
|
45 | } | |||
|
46 | ||||
38 | if (this.selector !== undefined) { |
|
47 | if (this.selector !== undefined) { | |
39 | this.element = $(selector); |
|
48 | this.element = $(selector); | |
40 | this.style(); |
|
49 | this.style(); | |
@@ -42,23 +51,24 b' var IPython = (function (IPython) {' | |||||
42 | } |
|
51 | } | |
43 | }; |
|
52 | }; | |
44 |
|
53 | |||
|
54 | // TODO: This has definitively nothing to do with style ... | |||
45 | MenuBar.prototype.style = function () { |
|
55 | MenuBar.prototype.style = function () { | |
46 | this.element.addClass('border-box-sizing'); |
|
56 | var that = this; | |
47 | this.element.find("li").click(function (event, ui) { |
|
57 | this.element.find("li").click(function (event, ui) { | |
48 | // The selected cell loses focus when the menu is entered, so we |
|
58 | // The selected cell loses focus when the menu is entered, so we | |
49 | // re-select it upon selection. |
|
59 | // re-select it upon selection. | |
50 |
var i = |
|
60 | var i = that.notebook.get_selected_index(); | |
51 |
|
|
61 | that.notebook.select(i); | |
52 | } |
|
62 | } | |
53 | ); |
|
63 | ); | |
54 | }; |
|
64 | }; | |
55 |
|
65 | |||
56 | MenuBar.prototype._nbconvert = function (format, download) { |
|
66 | MenuBar.prototype._nbconvert = function (format, download) { | |
57 | download = download || false; |
|
67 | download = download || false; | |
58 |
var notebook_path = |
|
68 | var notebook_path = this.notebook.notebook_path; | |
59 |
var notebook_name = |
|
69 | var notebook_name = this.notebook.notebook_name; | |
60 |
if ( |
|
70 | if (this.notebook.dirty) { | |
61 |
|
|
71 | this.notebook.save_notebook({async : false}); | |
62 | } |
|
72 | } | |
63 | var url = utils.url_join_encode( |
|
73 | var url = utils.url_join_encode( | |
64 | this.base_url, |
|
74 | this.base_url, | |
@@ -75,25 +85,25 b' var IPython = (function (IPython) {' | |||||
75 | // File |
|
85 | // File | |
76 | var that = this; |
|
86 | var that = this; | |
77 | this.element.find('#new_notebook').click(function () { |
|
87 | this.element.find('#new_notebook').click(function () { | |
78 |
|
|
88 | that.notebook.new_notebook(); | |
79 | }); |
|
89 | }); | |
80 | this.element.find('#open_notebook').click(function () { |
|
90 | this.element.find('#open_notebook').click(function () { | |
81 | window.open(utils.url_join_encode( |
|
91 | window.open(utils.url_join_encode( | |
82 |
|
|
92 | that.notebook.base_url, | |
83 | 'tree', |
|
93 | 'tree', | |
84 |
|
|
94 | that.notebook.notebook_path | |
85 | )); |
|
95 | )); | |
86 | }); |
|
96 | }); | |
87 | this.element.find('#copy_notebook').click(function () { |
|
97 | this.element.find('#copy_notebook').click(function () { | |
88 |
|
|
98 | that.notebook.copy_notebook(); | |
89 | return false; |
|
99 | return false; | |
90 | }); |
|
100 | }); | |
91 | this.element.find('#download_ipynb').click(function () { |
|
101 | this.element.find('#download_ipynb').click(function () { | |
92 |
var base_url = |
|
102 | var base_url = that.notebook.base_url; | |
93 |
var notebook_path = |
|
103 | var notebook_path = that.notebook.notebook_path; | |
94 |
var notebook_name = |
|
104 | var notebook_name = that.notebook.notebook_name; | |
95 |
if ( |
|
105 | if (that.notebook.dirty) { | |
96 |
|
|
106 | that.notebook.save_notebook({async : false}); | |
97 | } |
|
107 | } | |
98 |
|
108 | |||
99 | var url = utils.url_join_encode( |
|
109 | var url = utils.url_join_encode( | |
@@ -126,17 +136,17 b' var IPython = (function (IPython) {' | |||||
126 | }); |
|
136 | }); | |
127 |
|
137 | |||
128 | this.element.find('#rename_notebook').click(function () { |
|
138 | this.element.find('#rename_notebook').click(function () { | |
129 |
|
|
139 | that.save_widget.rename_notebook({notebook: that.notebook}); | |
130 | }); |
|
140 | }); | |
131 | this.element.find('#save_checkpoint').click(function () { |
|
141 | this.element.find('#save_checkpoint').click(function () { | |
132 |
|
|
142 | that.notebook.save_checkpoint(); | |
133 | }); |
|
143 | }); | |
134 | this.element.find('#restore_checkpoint').click(function () { |
|
144 | this.element.find('#restore_checkpoint').click(function () { | |
135 | }); |
|
145 | }); | |
136 | this.element.find('#trust_notebook').click(function () { |
|
146 | this.element.find('#trust_notebook').click(function () { | |
137 |
|
|
147 | that.notebook.trust_notebook(); | |
138 | }); |
|
148 | }); | |
139 |
|
|
149 | this.events.on('trust_changed.Notebook', function (event, trusted) { | |
140 | if (trusted) { |
|
150 | if (trusted) { | |
141 | that.element.find('#trust_notebook') |
|
151 | that.element.find('#trust_notebook') | |
142 | .addClass("disabled") |
|
152 | .addClass("disabled") | |
@@ -148,157 +158,160 b' var IPython = (function (IPython) {' | |||||
148 | } |
|
158 | } | |
149 | }); |
|
159 | }); | |
150 | this.element.find('#kill_and_exit').click(function () { |
|
160 | this.element.find('#kill_and_exit').click(function () { | |
151 | IPython.notebook.session.delete(); |
|
161 | var close_window = function () { | |
152 | setTimeout(function(){ |
|
|||
153 | // allow closing of new tabs in Chromium, impossible in FF |
|
162 | // allow closing of new tabs in Chromium, impossible in FF | |
154 | window.open('', '_self', ''); |
|
163 | window.open('', '_self', ''); | |
155 | window.close(); |
|
164 | window.close(); | |
156 |
} |
|
165 | }; | |
|
166 | // finish with close on success or failure | |||
|
167 | that.notebook.session.delete(close_window, close_window); | |||
157 | }); |
|
168 | }); | |
158 | // Edit |
|
169 | // Edit | |
159 | this.element.find('#cut_cell').click(function () { |
|
170 | this.element.find('#cut_cell').click(function () { | |
160 |
|
|
171 | that.notebook.cut_cell(); | |
161 | }); |
|
172 | }); | |
162 | this.element.find('#copy_cell').click(function () { |
|
173 | this.element.find('#copy_cell').click(function () { | |
163 |
|
|
174 | that.notebook.copy_cell(); | |
164 | }); |
|
175 | }); | |
165 | this.element.find('#delete_cell').click(function () { |
|
176 | this.element.find('#delete_cell').click(function () { | |
166 |
|
|
177 | that.notebook.delete_cell(); | |
167 | }); |
|
178 | }); | |
168 | this.element.find('#undelete_cell').click(function () { |
|
179 | this.element.find('#undelete_cell').click(function () { | |
169 |
|
|
180 | that.notebook.undelete_cell(); | |
170 | }); |
|
181 | }); | |
171 | this.element.find('#split_cell').click(function () { |
|
182 | this.element.find('#split_cell').click(function () { | |
172 |
|
|
183 | that.notebook.split_cell(); | |
173 | }); |
|
184 | }); | |
174 | this.element.find('#merge_cell_above').click(function () { |
|
185 | this.element.find('#merge_cell_above').click(function () { | |
175 |
|
|
186 | that.notebook.merge_cell_above(); | |
176 | }); |
|
187 | }); | |
177 | this.element.find('#merge_cell_below').click(function () { |
|
188 | this.element.find('#merge_cell_below').click(function () { | |
178 |
|
|
189 | that.notebook.merge_cell_below(); | |
179 | }); |
|
190 | }); | |
180 | this.element.find('#move_cell_up').click(function () { |
|
191 | this.element.find('#move_cell_up').click(function () { | |
181 |
|
|
192 | that.notebook.move_cell_up(); | |
182 | }); |
|
193 | }); | |
183 | this.element.find('#move_cell_down').click(function () { |
|
194 | this.element.find('#move_cell_down').click(function () { | |
184 |
|
|
195 | that.notebook.move_cell_down(); | |
185 | }); |
|
196 | }); | |
186 | this.element.find('#edit_nb_metadata').click(function () { |
|
197 | this.element.find('#edit_nb_metadata').click(function () { | |
187 |
|
|
198 | that.notebook.edit_metadata({ | |
|
199 | notebook: that.notebook, | |||
|
200 | keyboard_manager: that.notebook.keyboard_manager}); | |||
188 | }); |
|
201 | }); | |
189 |
|
202 | |||
190 | // View |
|
203 | // View | |
191 | this.element.find('#toggle_header').click(function () { |
|
204 | this.element.find('#toggle_header').click(function () { | |
192 | $('div#header').toggle(); |
|
205 | $('div#header').toggle(); | |
193 |
|
|
206 | that.layout_manager.do_resize(); | |
194 | }); |
|
207 | }); | |
195 | this.element.find('#toggle_toolbar').click(function () { |
|
208 | this.element.find('#toggle_toolbar').click(function () { | |
196 | $('div#maintoolbar').toggle(); |
|
209 | $('div#maintoolbar').toggle(); | |
197 |
|
|
210 | that.layout_manager.do_resize(); | |
198 | }); |
|
211 | }); | |
199 | // Insert |
|
212 | // Insert | |
200 | this.element.find('#insert_cell_above').click(function () { |
|
213 | this.element.find('#insert_cell_above').click(function () { | |
201 |
|
|
214 | that.notebook.insert_cell_above('code'); | |
202 |
|
|
215 | that.notebook.select_prev(); | |
203 | }); |
|
216 | }); | |
204 | this.element.find('#insert_cell_below').click(function () { |
|
217 | this.element.find('#insert_cell_below').click(function () { | |
205 |
|
|
218 | that.notebook.insert_cell_below('code'); | |
206 |
|
|
219 | that.notebook.select_next(); | |
207 | }); |
|
220 | }); | |
208 | // Cell |
|
221 | // Cell | |
209 | this.element.find('#run_cell').click(function () { |
|
222 | this.element.find('#run_cell').click(function () { | |
210 |
|
|
223 | that.notebook.execute_cell(); | |
211 | }); |
|
224 | }); | |
212 | this.element.find('#run_cell_select_below').click(function () { |
|
225 | this.element.find('#run_cell_select_below').click(function () { | |
213 |
|
|
226 | that.notebook.execute_cell_and_select_below(); | |
214 | }); |
|
227 | }); | |
215 | this.element.find('#run_cell_insert_below').click(function () { |
|
228 | this.element.find('#run_cell_insert_below').click(function () { | |
216 |
|
|
229 | that.notebook.execute_cell_and_insert_below(); | |
217 | }); |
|
230 | }); | |
218 | this.element.find('#run_all_cells').click(function () { |
|
231 | this.element.find('#run_all_cells').click(function () { | |
219 |
|
|
232 | that.notebook.execute_all_cells(); | |
220 | }); |
|
233 | }); | |
221 | this.element.find('#run_all_cells_above').click(function () { |
|
234 | this.element.find('#run_all_cells_above').click(function () { | |
222 |
|
|
235 | that.notebook.execute_cells_above(); | |
223 | }); |
|
236 | }); | |
224 | this.element.find('#run_all_cells_below').click(function () { |
|
237 | this.element.find('#run_all_cells_below').click(function () { | |
225 |
|
|
238 | that.notebook.execute_cells_below(); | |
226 | }); |
|
239 | }); | |
227 | this.element.find('#to_code').click(function () { |
|
240 | this.element.find('#to_code').click(function () { | |
228 |
|
|
241 | that.notebook.to_code(); | |
229 | }); |
|
242 | }); | |
230 | this.element.find('#to_markdown').click(function () { |
|
243 | this.element.find('#to_markdown').click(function () { | |
231 |
|
|
244 | that.notebook.to_markdown(); | |
232 | }); |
|
245 | }); | |
233 | this.element.find('#to_raw').click(function () { |
|
246 | this.element.find('#to_raw').click(function () { | |
234 |
|
|
247 | that.notebook.to_raw(); | |
235 | }); |
|
248 | }); | |
236 | this.element.find('#to_heading1').click(function () { |
|
249 | this.element.find('#to_heading1').click(function () { | |
237 |
|
|
250 | that.notebook.to_heading(undefined, 1); | |
238 | }); |
|
251 | }); | |
239 | this.element.find('#to_heading2').click(function () { |
|
252 | this.element.find('#to_heading2').click(function () { | |
240 |
|
|
253 | that.notebook.to_heading(undefined, 2); | |
241 | }); |
|
254 | }); | |
242 | this.element.find('#to_heading3').click(function () { |
|
255 | this.element.find('#to_heading3').click(function () { | |
243 |
|
|
256 | that.notebook.to_heading(undefined, 3); | |
244 | }); |
|
257 | }); | |
245 | this.element.find('#to_heading4').click(function () { |
|
258 | this.element.find('#to_heading4').click(function () { | |
246 |
|
|
259 | that.notebook.to_heading(undefined, 4); | |
247 | }); |
|
260 | }); | |
248 | this.element.find('#to_heading5').click(function () { |
|
261 | this.element.find('#to_heading5').click(function () { | |
249 |
|
|
262 | that.notebook.to_heading(undefined, 5); | |
250 | }); |
|
263 | }); | |
251 | this.element.find('#to_heading6').click(function () { |
|
264 | this.element.find('#to_heading6').click(function () { | |
252 |
|
|
265 | that.notebook.to_heading(undefined, 6); | |
253 | }); |
|
266 | }); | |
254 |
|
267 | |||
255 | this.element.find('#toggle_current_output').click(function () { |
|
268 | this.element.find('#toggle_current_output').click(function () { | |
256 |
|
|
269 | that.notebook.toggle_output(); | |
257 | }); |
|
270 | }); | |
258 | this.element.find('#toggle_current_output_scroll').click(function () { |
|
271 | this.element.find('#toggle_current_output_scroll').click(function () { | |
259 |
|
|
272 | that.notebook.toggle_output_scroll(); | |
260 | }); |
|
273 | }); | |
261 | this.element.find('#clear_current_output').click(function () { |
|
274 | this.element.find('#clear_current_output').click(function () { | |
262 |
|
|
275 | that.notebook.clear_output(); | |
263 | }); |
|
276 | }); | |
264 |
|
277 | |||
265 | this.element.find('#toggle_all_output').click(function () { |
|
278 | this.element.find('#toggle_all_output').click(function () { | |
266 |
|
|
279 | that.notebook.toggle_all_output(); | |
267 | }); |
|
280 | }); | |
268 | this.element.find('#toggle_all_output_scroll').click(function () { |
|
281 | this.element.find('#toggle_all_output_scroll').click(function () { | |
269 |
|
|
282 | that.notebook.toggle_all_output_scroll(); | |
270 | }); |
|
283 | }); | |
271 | this.element.find('#clear_all_output').click(function () { |
|
284 | this.element.find('#clear_all_output').click(function () { | |
272 |
|
|
285 | that.notebook.clear_all_output(); | |
273 | }); |
|
286 | }); | |
274 |
|
287 | |||
275 | // Kernel |
|
288 | // Kernel | |
276 | this.element.find('#int_kernel').click(function () { |
|
289 | this.element.find('#int_kernel').click(function () { | |
277 |
|
|
290 | that.notebook.session.interrupt_kernel(); | |
278 | }); |
|
291 | }); | |
279 | this.element.find('#restart_kernel').click(function () { |
|
292 | this.element.find('#restart_kernel').click(function () { | |
280 |
|
|
293 | that.notebook.restart_kernel(); | |
281 | }); |
|
294 | }); | |
282 | // Help |
|
295 | // Help | |
283 |
if ( |
|
296 | if (this.tour) { | |
284 | this.element.find('#notebook_tour').click(function () { |
|
297 | this.element.find('#notebook_tour').click(function () { | |
285 |
|
|
298 | that.tour.start(); | |
286 | }); |
|
299 | }); | |
287 | } else { |
|
300 | } else { | |
288 | this.element.find('#notebook_tour').addClass("disabled"); |
|
301 | this.element.find('#notebook_tour').addClass("disabled"); | |
289 | } |
|
302 | } | |
290 | this.element.find('#keyboard_shortcuts').click(function () { |
|
303 | this.element.find('#keyboard_shortcuts').click(function () { | |
291 |
|
|
304 | that.quick_help.show_keyboard_shortcuts(); | |
292 | }); |
|
305 | }); | |
293 |
|
306 | |||
294 | this.update_restore_checkpoint(null); |
|
307 | this.update_restore_checkpoint(null); | |
295 |
|
308 | |||
296 |
|
|
309 | this.events.on('checkpoints_listed.Notebook', function (event, data) { | |
297 |
that.update_restore_checkpoint( |
|
310 | that.update_restore_checkpoint(that.notebook.checkpoints); | |
298 | }); |
|
311 | }); | |
299 |
|
312 | |||
300 |
|
|
313 | this.events.on('checkpoint_created.Notebook', function (event, data) { | |
301 |
that.update_restore_checkpoint( |
|
314 | that.update_restore_checkpoint(that.notebook.checkpoints); | |
302 | }); |
|
315 | }); | |
303 | }; |
|
316 | }; | |
304 |
|
317 | |||
@@ -317,23 +330,24 b' var IPython = (function (IPython) {' | |||||
317 | return; |
|
330 | return; | |
318 | } |
|
331 | } | |
319 |
|
332 | |||
|
333 | var that = this; | |||
320 | checkpoints.map(function (checkpoint) { |
|
334 | checkpoints.map(function (checkpoint) { | |
321 | var d = new Date(checkpoint.last_modified); |
|
335 | var d = new Date(checkpoint.last_modified); | |
322 | ul.append( |
|
336 | ul.append( | |
323 | $("<li/>").append( |
|
337 | $("<li/>").append( | |
324 | $("<a/>") |
|
338 | $("<a/>") | |
325 | .attr("href", "#") |
|
339 | .attr("href", "#") | |
326 |
.text(d.format(" |
|
340 | .text(moment(d).format("LLLL")) | |
327 | .click(function () { |
|
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 | IPython.MenuBar = MenuBar; |
|
350 | IPython.MenuBar = MenuBar; | |
336 |
|
351 | |||
337 | return IPython; |
|
352 | return {'MenuBar': MenuBar}; | |
338 |
|
353 | }); | ||
339 | }(IPython)); |
|
This diff has been collapsed as it changes many lines, (526 lines changed) Show them Hide them | |||||
@@ -1,32 +1,99 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
3 | ||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
4 | define([ | |
5 | // the file COPYING, distributed as part of this software. |
|
5 | 'base/js/namespace', | |
6 | //---------------------------------------------------------------------------- |
|
6 | 'jquery', | |
|
7 | 'base/js/utils', | |||
|
8 | 'base/js/dialog', | |||
|
9 | 'notebook/js/textcell', | |||
|
10 | 'notebook/js/codecell', | |||
|
11 | 'services/sessions/js/session', | |||
|
12 | 'notebook/js/celltoolbar', | |||
|
13 | 'components/marked/lib/marked', | |||
|
14 | 'highlight', | |||
|
15 | 'notebook/js/mathjaxutils', | |||
|
16 | 'base/js/keyboard', | |||
|
17 | 'notebook/js/tooltip', | |||
|
18 | 'notebook/js/celltoolbarpresets/default', | |||
|
19 | 'notebook/js/celltoolbarpresets/rawcell', | |||
|
20 | 'notebook/js/celltoolbarpresets/slideshow', | |||
|
21 | ], function ( | |||
|
22 | IPython, | |||
|
23 | $, | |||
|
24 | utils, | |||
|
25 | dialog, | |||
|
26 | textcell, | |||
|
27 | codecell, | |||
|
28 | session, | |||
|
29 | celltoolbar, | |||
|
30 | marked, | |||
|
31 | hljs, | |||
|
32 | mathjaxutils, | |||
|
33 | keyboard, | |||
|
34 | tooltip, | |||
|
35 | default_celltoolbar, | |||
|
36 | rawcell_celltoolbar, | |||
|
37 | slideshow_celltoolbar | |||
|
38 | ) { | |||
7 |
|
39 | |||
8 | //============================================================================ |
|
|||
9 | // Notebook |
|
|||
10 | //============================================================================ |
|
|||
11 |
|
||||
12 | var IPython = (function (IPython) { |
|
|||
13 | "use strict"; |
|
|||
14 |
|
||||
15 | var utils = IPython.utils; |
|
|||
16 |
|
||||
17 | /** |
|
|||
18 | * A notebook contains and manages cells. |
|
|||
19 | * |
|
|||
20 | * @class Notebook |
|
|||
21 | * @constructor |
|
|||
22 | * @param {String} selector A jQuery selector for the notebook's DOM element |
|
|||
23 | * @param {Object} [options] A config object |
|
|||
24 | */ |
|
|||
25 | var Notebook = function (selector, options) { |
|
40 | var Notebook = function (selector, options) { | |
26 | this.options = options = options || {}; |
|
41 | // Constructor | |
|
42 | // | |||
|
43 | // A notebook contains and manages cells. | |||
|
44 | // | |||
|
45 | // Parameters: | |||
|
46 | // selector: string | |||
|
47 | // options: dictionary | |||
|
48 | // Dictionary of keyword arguments. | |||
|
49 | // events: $(Events) instance | |||
|
50 | // keyboard_manager: KeyboardManager instance | |||
|
51 | // save_widget: SaveWidget instance | |||
|
52 | // config: dictionary | |||
|
53 | // base_url : string | |||
|
54 | // notebook_path : string | |||
|
55 | // notebook_name : string | |||
|
56 | this.config = options.config || {}; | |||
27 | this.base_url = options.base_url; |
|
57 | this.base_url = options.base_url; | |
28 | this.notebook_path = options.notebook_path; |
|
58 | this.notebook_path = options.notebook_path; | |
29 | this.notebook_name = options.notebook_name; |
|
59 | this.notebook_name = options.notebook_name; | |
|
60 | this.events = options.events; | |||
|
61 | this.keyboard_manager = options.keyboard_manager; | |||
|
62 | this.save_widget = options.save_widget; | |||
|
63 | this.tooltip = new tooltip.Tooltip(this.events); | |||
|
64 | this.ws_url = options.ws_url; | |||
|
65 | this._session_starting = false; | |||
|
66 | // default_kernel_name is a temporary measure while we implement proper | |||
|
67 | // kernel selection and delayed start. Do not rely on it. | |||
|
68 | this.default_kernel_name = 'python'; | |||
|
69 | // TODO: This code smells (and the other `= this` line a couple lines down) | |||
|
70 | // We need a better way to deal with circular instance references. | |||
|
71 | this.keyboard_manager.notebook = this; | |||
|
72 | this.save_widget.notebook = this; | |||
|
73 | ||||
|
74 | mathjaxutils.init(); | |||
|
75 | ||||
|
76 | if (marked) { | |||
|
77 | marked.setOptions({ | |||
|
78 | gfm : true, | |||
|
79 | tables: true, | |||
|
80 | langPrefix: "language-", | |||
|
81 | highlight: function(code, lang) { | |||
|
82 | if (!lang) { | |||
|
83 | // no language, no highlight | |||
|
84 | return code; | |||
|
85 | } | |||
|
86 | var highlighted; | |||
|
87 | try { | |||
|
88 | highlighted = hljs.highlight(lang, code, false); | |||
|
89 | } catch(err) { | |||
|
90 | highlighted = hljs.highlightAuto(code); | |||
|
91 | } | |||
|
92 | return highlighted.value; | |||
|
93 | } | |||
|
94 | }); | |||
|
95 | } | |||
|
96 | ||||
30 | this.element = $(selector); |
|
97 | this.element = $(selector); | |
31 | this.element.scroll(); |
|
98 | this.element.scroll(); | |
32 | this.element.data("notebook", this); |
|
99 | this.element.data("notebook", this); | |
@@ -55,23 +122,20 b' var IPython = (function (IPython) {' | |||||
55 | this.notebook_name_blacklist_re = /[\/\\:]/; |
|
122 | this.notebook_name_blacklist_re = /[\/\\:]/; | |
56 | this.nbformat = 3; // Increment this when changing the nbformat |
|
123 | this.nbformat = 3; // Increment this when changing the nbformat | |
57 | this.nbformat_minor = 0; // Increment this when changing the nbformat |
|
124 | this.nbformat_minor = 0; // Increment this when changing the nbformat | |
58 | this.style(); |
|
125 | this.codemirror_mode = 'ipython'; | |
59 | this.create_elements(); |
|
126 | this.create_elements(); | |
60 | this.bind_events(); |
|
127 | this.bind_events(); | |
61 | this.save_notebook = function() { // don't allow save until notebook_loaded |
|
128 | this.save_notebook = function() { // don't allow save until notebook_loaded | |
62 | this.save_notebook_error(null, null, "Load failed, save is disabled"); |
|
129 | this.save_notebook_error(null, null, "Load failed, save is disabled"); | |
63 | }; |
|
130 | }; | |
64 | }; |
|
|||
65 |
|
131 | |||
66 | /** |
|
132 | // Trigger cell toolbar registration. | |
67 | * Tweak the notebook's CSS style. |
|
133 | default_celltoolbar.register(this); | |
68 | * |
|
134 | rawcell_celltoolbar.register(this); | |
69 | * @method style |
|
135 | slideshow_celltoolbar.register(this); | |
70 | */ |
|
|||
71 | Notebook.prototype.style = function () { |
|
|||
72 | $('div#notebook').addClass('border-box-sizing'); |
|
|||
73 | }; |
|
136 | }; | |
74 |
|
137 | |||
|
138 | ||||
75 | /** |
|
139 | /** | |
76 | * Create an HTML and CSS representation of the notebook. |
|
140 | * Create an HTML and CSS representation of the notebook. | |
77 | * |
|
141 | * | |
@@ -102,36 +166,38 b' var IPython = (function (IPython) {' | |||||
102 | Notebook.prototype.bind_events = function () { |
|
166 | Notebook.prototype.bind_events = function () { | |
103 | var that = this; |
|
167 | var that = this; | |
104 |
|
168 | |||
105 |
|
|
169 | this.events.on('set_next_input.Notebook', function (event, data) { | |
106 | var index = that.find_cell_index(data.cell); |
|
170 | var index = that.find_cell_index(data.cell); | |
107 | var new_cell = that.insert_cell_below('code',index); |
|
171 | var new_cell = that.insert_cell_below('code',index); | |
108 | new_cell.set_text(data.text); |
|
172 | new_cell.set_text(data.text); | |
109 | that.dirty = true; |
|
173 | that.dirty = true; | |
110 | }); |
|
174 | }); | |
111 |
|
175 | |||
112 |
|
|
176 | this.events.on('set_dirty.Notebook', function (event, data) { | |
113 | that.dirty = data.value; |
|
177 | that.dirty = data.value; | |
114 | }); |
|
178 | }); | |
115 |
|
179 | |||
116 |
|
|
180 | this.events.on('trust_changed.Notebook', function (event, data) { | |
117 | that.trusted = data.value; |
|
181 | that.trusted = data.value; | |
118 | }); |
|
182 | }); | |
119 |
|
183 | |||
120 |
|
|
184 | this.events.on('select.Cell', function (event, data) { | |
121 | var index = that.find_cell_index(data.cell); |
|
185 | var index = that.find_cell_index(data.cell); | |
122 | that.select(index); |
|
186 | that.select(index); | |
123 | }); |
|
187 | }); | |
124 |
|
188 | |||
125 |
|
|
189 | this.events.on('edit_mode.Cell', function (event, data) { | |
126 | that.handle_edit_mode(data.cell); |
|
190 | that.handle_edit_mode(data.cell); | |
127 | }); |
|
191 | }); | |
128 |
|
192 | |||
129 |
|
|
193 | this.events.on('command_mode.Cell', function (event, data) { | |
130 | that.handle_command_mode(data.cell); |
|
194 | that.handle_command_mode(data.cell); | |
131 | }); |
|
195 | }); | |
132 |
|
196 | |||
133 |
|
|
197 | this.events.on('status_autorestarting.Kernel', function () { | |
134 |
|
|
198 | dialog.modal({ | |
|
199 | notebook: that, | |||
|
200 | keyboard_manager: that.keyboard_manager, | |||
135 | title: "Kernel Restarting", |
|
201 | title: "Kernel Restarting", | |
136 | body: "The kernel appears to have died. It will restart automatically.", |
|
202 | body: "The kernel appears to have died. It will restart automatically.", | |
137 | buttons: { |
|
203 | buttons: { | |
@@ -141,6 +207,13 b' var IPython = (function (IPython) {' | |||||
141 | } |
|
207 | } | |
142 | }); |
|
208 | }); | |
143 | }); |
|
209 | }); | |
|
210 | ||||
|
211 | this.events.on('spec_changed.Kernel', function(event, data) { | |||
|
212 | that.set_kernelspec_metadata(data); | |||
|
213 | if (data.codemirror_mode) { | |||
|
214 | that.set_codemirror_mode(data.codemirror_mode); | |||
|
215 | } | |||
|
216 | }); | |||
144 |
|
217 | |||
145 | var collapse_time = function (time) { |
|
218 | var collapse_time = function (time) { | |
146 | var app_height = $('#ipython-main-app').height(); // content height |
|
219 | var app_height = $('#ipython-main-app').height(); // content height | |
@@ -211,7 +284,7 b' var IPython = (function (IPython) {' | |||||
211 | if (this.dirty == value) { |
|
284 | if (this.dirty == value) { | |
212 | return; |
|
285 | return; | |
213 | } |
|
286 | } | |
214 |
|
|
287 | this.events.trigger('set_dirty.Notebook', {value: value}); | |
215 | }; |
|
288 | }; | |
216 |
|
289 | |||
217 | /** |
|
290 | /** | |
@@ -254,10 +327,25 b' var IPython = (function (IPython) {' | |||||
254 |
|
327 | |||
255 | Notebook.prototype.edit_metadata = function () { |
|
328 | Notebook.prototype.edit_metadata = function () { | |
256 | var that = this; |
|
329 | var that = this; | |
257 |
|
|
330 | dialog.edit_metadata({ | |
258 |
|
|
331 | md: this.metadata, | |
259 | }, 'Notebook'); |
|
332 | callback: function (md) { | |
|
333 | that.metadata = md; | |||
|
334 | }, | |||
|
335 | name: 'Notebook', | |||
|
336 | notebook: this, | |||
|
337 | keyboard_manager: this.keyboard_manager}); | |||
260 | }; |
|
338 | }; | |
|
339 | ||||
|
340 | Notebook.prototype.set_kernelspec_metadata = function(ks) { | |||
|
341 | var tostore = {}; | |||
|
342 | $.map(ks, function(value, field) { | |||
|
343 | if (field !== 'argv' && field !== 'env') { | |||
|
344 | tostore[field] = value; | |||
|
345 | } | |||
|
346 | }); | |||
|
347 | this.metadata.kernelspec = tostore; | |||
|
348 | } | |||
261 |
|
349 | |||
262 | // Cell indexing, retrieval, etc. |
|
350 | // Cell indexing, retrieval, etc. | |
263 |
|
351 | |||
@@ -295,7 +383,7 b' var IPython = (function (IPython) {' | |||||
295 | * @return {Cell} Cell or null if no cell was found. |
|
383 | * @return {Cell} Cell or null if no cell was found. | |
296 | */ |
|
384 | */ | |
297 | Notebook.prototype.get_msg_cell = function (msg_id) { |
|
385 | Notebook.prototype.get_msg_cell = function (msg_id) { | |
298 |
return |
|
386 | return codecell.CodeCell.msg_cells[msg_id] || null; | |
299 | }; |
|
387 | }; | |
300 |
|
388 | |||
301 | /** |
|
389 | /** | |
@@ -474,11 +562,11 b' var IPython = (function (IPython) {' | |||||
474 | var cell = this.get_cell(index); |
|
562 | var cell = this.get_cell(index); | |
475 | cell.select(); |
|
563 | cell.select(); | |
476 | if (cell.cell_type === 'heading') { |
|
564 | if (cell.cell_type === 'heading') { | |
477 |
|
|
565 | this.events.trigger('selected_cell_type_changed.Notebook', | |
478 | {'cell_type':cell.cell_type,level:cell.level} |
|
566 | {'cell_type':cell.cell_type,level:cell.level} | |
479 | ); |
|
567 | ); | |
480 | } else { |
|
568 | } else { | |
481 |
|
|
569 | this.events.trigger('selected_cell_type_changed.Notebook', | |
482 | {'cell_type':cell.cell_type} |
|
570 | {'cell_type':cell.cell_type} | |
483 | ); |
|
571 | ); | |
484 | } |
|
572 | } | |
@@ -540,8 +628,8 b' var IPython = (function (IPython) {' | |||||
540 | if (this.mode !== 'command') { |
|
628 | if (this.mode !== 'command') { | |
541 | cell.command_mode(); |
|
629 | cell.command_mode(); | |
542 | this.mode = 'command'; |
|
630 | this.mode = 'command'; | |
543 |
|
|
631 | this.events.trigger('command_mode.Notebook'); | |
544 |
|
|
632 | this.keyboard_manager.command_mode(); | |
545 | } |
|
633 | } | |
546 | }; |
|
634 | }; | |
547 |
|
635 | |||
@@ -570,8 +658,8 b' var IPython = (function (IPython) {' | |||||
570 | if (cell && this.mode !== 'edit') { |
|
658 | if (cell && this.mode !== 'edit') { | |
571 | cell.edit_mode(); |
|
659 | cell.edit_mode(); | |
572 | this.mode = 'edit'; |
|
660 | this.mode = 'edit'; | |
573 |
|
|
661 | this.events.trigger('edit_mode.Notebook'); | |
574 |
|
|
662 | this.keyboard_manager.edit_mode(); | |
575 | } |
|
663 | } | |
576 | }; |
|
664 | }; | |
577 |
|
665 | |||
@@ -686,7 +774,7 b' var IPython = (function (IPython) {' | |||||
686 | this.undelete_index = i; |
|
774 | this.undelete_index = i; | |
687 | this.undelete_below = false; |
|
775 | this.undelete_below = false; | |
688 | } |
|
776 | } | |
689 |
|
|
777 | this.events.trigger('delete.Cell', {'cell': cell, 'index': i}); | |
690 | this.set_dirty(true); |
|
778 | this.set_dirty(true); | |
691 | } |
|
779 | } | |
692 | return this; |
|
780 | return this; | |
@@ -753,20 +841,27 b' var IPython = (function (IPython) {' | |||||
753 | type = type || this.get_selected_cell().cell_type; |
|
841 | type = type || this.get_selected_cell().cell_type; | |
754 |
|
842 | |||
755 | if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) { |
|
843 | if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) { | |
|
844 | var cell_options = { | |||
|
845 | events: this.events, | |||
|
846 | config: this.config, | |||
|
847 | keyboard_manager: this.keyboard_manager, | |||
|
848 | notebook: this, | |||
|
849 | tooltip: this.tooltip, | |||
|
850 | }; | |||
756 | if (type === 'code') { |
|
851 | if (type === 'code') { | |
757 |
cell = new |
|
852 | cell = new codecell.CodeCell(this.kernel, cell_options); | |
758 | cell.set_input_prompt(); |
|
853 | cell.set_input_prompt(); | |
759 | } else if (type === 'markdown') { |
|
854 | } else if (type === 'markdown') { | |
760 |
cell = new |
|
855 | cell = new textcell.MarkdownCell(cell_options); | |
761 | } else if (type === 'raw') { |
|
856 | } else if (type === 'raw') { | |
762 |
cell = new |
|
857 | cell = new textcell.RawCell(cell_options); | |
763 | } else if (type === 'heading') { |
|
858 | } else if (type === 'heading') { | |
764 |
cell = new |
|
859 | cell = new textcell.HeadingCell(cell_options); | |
765 | } |
|
860 | } | |
766 |
|
861 | |||
767 | if(this._insert_element_at_index(cell.element,index)) { |
|
862 | if(this._insert_element_at_index(cell.element,index)) { | |
768 | cell.render(); |
|
863 | cell.render(); | |
769 |
|
|
864 | this.events.trigger('create.Cell', {'cell': cell, 'index': index}); | |
770 | cell.refresh(); |
|
865 | cell.refresh(); | |
771 | // We used to select the cell after we refresh it, but there |
|
866 | // We used to select the cell after we refresh it, but there | |
772 | // are now cases were this method is called where select is |
|
867 | // are now cases were this method is called where select is | |
@@ -876,7 +971,7 b' var IPython = (function (IPython) {' | |||||
876 | if (this.is_valid_cell_index(i)) { |
|
971 | if (this.is_valid_cell_index(i)) { | |
877 | var source_element = this.get_cell_element(i); |
|
972 | var source_element = this.get_cell_element(i); | |
878 | var source_cell = source_element.data("cell"); |
|
973 | var source_cell = source_element.data("cell"); | |
879 |
if (!(source_cell instanceof |
|
974 | if (!(source_cell instanceof codecell.CodeCell)) { | |
880 | var target_cell = this.insert_cell_below('code',i); |
|
975 | var target_cell = this.insert_cell_below('code',i); | |
881 | var text = source_cell.get_text(); |
|
976 | var text = source_cell.get_text(); | |
882 | if (text === source_cell.placeholder) { |
|
977 | if (text === source_cell.placeholder) { | |
@@ -906,7 +1001,7 b' var IPython = (function (IPython) {' | |||||
906 | if (this.is_valid_cell_index(i)) { |
|
1001 | if (this.is_valid_cell_index(i)) { | |
907 | var source_element = this.get_cell_element(i); |
|
1002 | var source_element = this.get_cell_element(i); | |
908 | var source_cell = source_element.data("cell"); |
|
1003 | var source_cell = source_element.data("cell"); | |
909 |
if (!(source_cell instanceof |
|
1004 | if (!(source_cell instanceof textcell.MarkdownCell)) { | |
910 | var target_cell = this.insert_cell_below('markdown',i); |
|
1005 | var target_cell = this.insert_cell_below('markdown',i); | |
911 | var text = source_cell.get_text(); |
|
1006 | var text = source_cell.get_text(); | |
912 | if (text === source_cell.placeholder) { |
|
1007 | if (text === source_cell.placeholder) { | |
@@ -920,7 +1015,7 b' var IPython = (function (IPython) {' | |||||
920 | target_cell.code_mirror.clearHistory(); |
|
1015 | target_cell.code_mirror.clearHistory(); | |
921 | source_element.remove(); |
|
1016 | source_element.remove(); | |
922 | this.select(i); |
|
1017 | this.select(i); | |
923 |
if ((source_cell instanceof |
|
1018 | if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) { | |
924 | target_cell.render(); |
|
1019 | target_cell.render(); | |
925 | } |
|
1020 | } | |
926 | var cursor = source_cell.code_mirror.getCursor(); |
|
1021 | var cursor = source_cell.code_mirror.getCursor(); | |
@@ -942,7 +1037,7 b' var IPython = (function (IPython) {' | |||||
942 | var source_element = this.get_cell_element(i); |
|
1037 | var source_element = this.get_cell_element(i); | |
943 | var source_cell = source_element.data("cell"); |
|
1038 | var source_cell = source_element.data("cell"); | |
944 | var target_cell = null; |
|
1039 | var target_cell = null; | |
945 |
if (!(source_cell instanceof |
|
1040 | if (!(source_cell instanceof textcell.RawCell)) { | |
946 | target_cell = this.insert_cell_below('raw',i); |
|
1041 | target_cell = this.insert_cell_below('raw',i); | |
947 | var text = source_cell.get_text(); |
|
1042 | var text = source_cell.get_text(); | |
948 | if (text === source_cell.placeholder) { |
|
1043 | if (text === source_cell.placeholder) { | |
@@ -977,7 +1072,7 b' var IPython = (function (IPython) {' | |||||
977 | var source_element = this.get_cell_element(i); |
|
1072 | var source_element = this.get_cell_element(i); | |
978 | var source_cell = source_element.data("cell"); |
|
1073 | var source_cell = source_element.data("cell"); | |
979 | var target_cell = null; |
|
1074 | var target_cell = null; | |
980 |
if (source_cell instanceof |
|
1075 | if (source_cell instanceof textcell.HeadingCell) { | |
981 | source_cell.set_level(level); |
|
1076 | source_cell.set_level(level); | |
982 | } else { |
|
1077 | } else { | |
983 | target_cell = this.insert_cell_below('heading',i); |
|
1078 | target_cell = this.insert_cell_below('heading',i); | |
@@ -996,12 +1091,12 b' var IPython = (function (IPython) {' | |||||
996 | this.select(i); |
|
1091 | this.select(i); | |
997 | var cursor = source_cell.code_mirror.getCursor(); |
|
1092 | var cursor = source_cell.code_mirror.getCursor(); | |
998 | target_cell.code_mirror.setCursor(cursor); |
|
1093 | target_cell.code_mirror.setCursor(cursor); | |
999 |
if ((source_cell instanceof |
|
1094 | if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) { | |
1000 | target_cell.render(); |
|
1095 | target_cell.render(); | |
1001 | } |
|
1096 | } | |
1002 | } |
|
1097 | } | |
1003 | this.set_dirty(true); |
|
1098 | this.set_dirty(true); | |
1004 |
|
|
1099 | this.events.trigger('selected_cell_type_changed.Notebook', | |
1005 | {'cell_type':'heading',level:level} |
|
1100 | {'cell_type':'heading',level:level} | |
1006 | ); |
|
1101 | ); | |
1007 | } |
|
1102 | } | |
@@ -1115,26 +1210,17 b' var IPython = (function (IPython) {' | |||||
1115 | * @method split_cell |
|
1210 | * @method split_cell | |
1116 | */ |
|
1211 | */ | |
1117 | Notebook.prototype.split_cell = function () { |
|
1212 | Notebook.prototype.split_cell = function () { | |
1118 |
var mdc = |
|
1213 | var mdc = textcell.MarkdownCell; | |
1119 |
var rc = |
|
1214 | var rc = textcell.RawCell; | |
1120 | var cell = this.get_selected_cell(); |
|
1215 | var cell = this.get_selected_cell(); | |
1121 | if (cell.is_splittable()) { |
|
1216 | if (cell.is_splittable()) { | |
1122 | var texta = cell.get_pre_cursor(); |
|
1217 | var texta = cell.get_pre_cursor(); | |
1123 | var textb = cell.get_post_cursor(); |
|
1218 | var textb = cell.get_post_cursor(); | |
1124 | if (cell instanceof IPython.CodeCell) { |
|
1219 | cell.set_text(textb); | |
1125 | // In this case the operations keep the notebook in its existing mode |
|
1220 | var new_cell = this.insert_cell_above(cell.cell_type); | |
1126 | // so we don't need to do any post-op mode changes. |
|
1221 | // Unrender the new cell so we can call set_text. | |
1127 |
|
|
1222 | new_cell.unrender(); | |
1128 | var new_cell = this.insert_cell_above('code'); |
|
1223 | new_cell.set_text(texta); | |
1129 | new_cell.set_text(texta); |
|
|||
1130 | } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) { |
|
|||
1131 | // We know cell is !rendered so we can use set_text. |
|
|||
1132 | cell.set_text(textb); |
|
|||
1133 | var new_cell = this.insert_cell_above(cell.cell_type); |
|
|||
1134 | // Unrender the new cell so we can call set_text. |
|
|||
1135 | new_cell.unrender(); |
|
|||
1136 | new_cell.set_text(texta); |
|
|||
1137 | } |
|
|||
1138 | } |
|
1224 | } | |
1139 | }; |
|
1225 | }; | |
1140 |
|
1226 | |||
@@ -1144,8 +1230,8 b' var IPython = (function (IPython) {' | |||||
1144 | * @method merge_cell_above |
|
1230 | * @method merge_cell_above | |
1145 | */ |
|
1231 | */ | |
1146 | Notebook.prototype.merge_cell_above = function () { |
|
1232 | Notebook.prototype.merge_cell_above = function () { | |
1147 |
var mdc = |
|
1233 | var mdc = textcell.MarkdownCell; | |
1148 |
var rc = |
|
1234 | var rc = textcell.RawCell; | |
1149 | var index = this.get_selected_index(); |
|
1235 | var index = this.get_selected_index(); | |
1150 | var cell = this.get_cell(index); |
|
1236 | var cell = this.get_cell(index); | |
1151 | var render = cell.rendered; |
|
1237 | var render = cell.rendered; | |
@@ -1159,9 +1245,9 b' var IPython = (function (IPython) {' | |||||
1159 | } |
|
1245 | } | |
1160 | var upper_text = upper_cell.get_text(); |
|
1246 | var upper_text = upper_cell.get_text(); | |
1161 | var text = cell.get_text(); |
|
1247 | var text = cell.get_text(); | |
1162 |
if (cell instanceof |
|
1248 | if (cell instanceof codecell.CodeCell) { | |
1163 | cell.set_text(upper_text+'\n'+text); |
|
1249 | cell.set_text(upper_text+'\n'+text); | |
1164 | } else if ((cell instanceof mdc) || (cell instanceof rc)) { |
|
1250 | } else { | |
1165 | cell.unrender(); // Must unrender before we set_text. |
|
1251 | cell.unrender(); // Must unrender before we set_text. | |
1166 | cell.set_text(upper_text+'\n\n'+text); |
|
1252 | cell.set_text(upper_text+'\n\n'+text); | |
1167 | if (render) { |
|
1253 | if (render) { | |
@@ -1181,8 +1267,8 b' var IPython = (function (IPython) {' | |||||
1181 | * @method merge_cell_below |
|
1267 | * @method merge_cell_below | |
1182 | */ |
|
1268 | */ | |
1183 | Notebook.prototype.merge_cell_below = function () { |
|
1269 | Notebook.prototype.merge_cell_below = function () { | |
1184 |
var mdc = |
|
1270 | var mdc = textcell.MarkdownCell; | |
1185 |
var rc = |
|
1271 | var rc = textcell.RawCell; | |
1186 | var index = this.get_selected_index(); |
|
1272 | var index = this.get_selected_index(); | |
1187 | var cell = this.get_cell(index); |
|
1273 | var cell = this.get_cell(index); | |
1188 | var render = cell.rendered; |
|
1274 | var render = cell.rendered; | |
@@ -1196,9 +1282,9 b' var IPython = (function (IPython) {' | |||||
1196 | } |
|
1282 | } | |
1197 | var lower_text = lower_cell.get_text(); |
|
1283 | var lower_text = lower_cell.get_text(); | |
1198 | var text = cell.get_text(); |
|
1284 | var text = cell.get_text(); | |
1199 |
if (cell instanceof |
|
1285 | if (cell instanceof codecell.CodeCell) { | |
1200 | cell.set_text(text+'\n'+lower_text); |
|
1286 | cell.set_text(text+'\n'+lower_text); | |
1201 | } else if ((cell instanceof mdc) || (cell instanceof rc)) { |
|
1287 | } else { | |
1202 | cell.unrender(); // Must unrender before we set_text. |
|
1288 | cell.unrender(); // Must unrender before we set_text. | |
1203 | cell.set_text(text+'\n\n'+lower_text); |
|
1289 | cell.set_text(text+'\n\n'+lower_text); | |
1204 | if (render) { |
|
1290 | if (render) { | |
@@ -1224,7 +1310,7 b' var IPython = (function (IPython) {' | |||||
1224 | Notebook.prototype.collapse_output = function (index) { |
|
1310 | Notebook.prototype.collapse_output = function (index) { | |
1225 | var i = this.index_or_selected(index); |
|
1311 | var i = this.index_or_selected(index); | |
1226 | var cell = this.get_cell(i); |
|
1312 | var cell = this.get_cell(i); | |
1227 |
if (cell !== null && (cell instanceof |
|
1313 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
1228 | cell.collapse_output(); |
|
1314 | cell.collapse_output(); | |
1229 | this.set_dirty(true); |
|
1315 | this.set_dirty(true); | |
1230 | } |
|
1316 | } | |
@@ -1237,7 +1323,7 b' var IPython = (function (IPython) {' | |||||
1237 | */ |
|
1323 | */ | |
1238 | Notebook.prototype.collapse_all_output = function () { |
|
1324 | Notebook.prototype.collapse_all_output = function () { | |
1239 | $.map(this.get_cells(), function (cell, i) { |
|
1325 | $.map(this.get_cells(), function (cell, i) { | |
1240 |
if (cell instanceof |
|
1326 | if (cell instanceof codecell.CodeCell) { | |
1241 | cell.collapse_output(); |
|
1327 | cell.collapse_output(); | |
1242 | } |
|
1328 | } | |
1243 | }); |
|
1329 | }); | |
@@ -1254,7 +1340,7 b' var IPython = (function (IPython) {' | |||||
1254 | Notebook.prototype.expand_output = function (index) { |
|
1340 | Notebook.prototype.expand_output = function (index) { | |
1255 | var i = this.index_or_selected(index); |
|
1341 | var i = this.index_or_selected(index); | |
1256 | var cell = this.get_cell(i); |
|
1342 | var cell = this.get_cell(i); | |
1257 |
if (cell !== null && (cell instanceof |
|
1343 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
1258 | cell.expand_output(); |
|
1344 | cell.expand_output(); | |
1259 | this.set_dirty(true); |
|
1345 | this.set_dirty(true); | |
1260 | } |
|
1346 | } | |
@@ -1267,7 +1353,7 b' var IPython = (function (IPython) {' | |||||
1267 | */ |
|
1353 | */ | |
1268 | Notebook.prototype.expand_all_output = function () { |
|
1354 | Notebook.prototype.expand_all_output = function () { | |
1269 | $.map(this.get_cells(), function (cell, i) { |
|
1355 | $.map(this.get_cells(), function (cell, i) { | |
1270 |
if (cell instanceof |
|
1356 | if (cell instanceof codecell.CodeCell) { | |
1271 | cell.expand_output(); |
|
1357 | cell.expand_output(); | |
1272 | } |
|
1358 | } | |
1273 | }); |
|
1359 | }); | |
@@ -1284,7 +1370,7 b' var IPython = (function (IPython) {' | |||||
1284 | Notebook.prototype.clear_output = function (index) { |
|
1370 | Notebook.prototype.clear_output = function (index) { | |
1285 | var i = this.index_or_selected(index); |
|
1371 | var i = this.index_or_selected(index); | |
1286 | var cell = this.get_cell(i); |
|
1372 | var cell = this.get_cell(i); | |
1287 |
if (cell !== null && (cell instanceof |
|
1373 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
1288 | cell.clear_output(); |
|
1374 | cell.clear_output(); | |
1289 | this.set_dirty(true); |
|
1375 | this.set_dirty(true); | |
1290 | } |
|
1376 | } | |
@@ -1297,7 +1383,7 b' var IPython = (function (IPython) {' | |||||
1297 | */ |
|
1383 | */ | |
1298 | Notebook.prototype.clear_all_output = function () { |
|
1384 | Notebook.prototype.clear_all_output = function () { | |
1299 | $.map(this.get_cells(), function (cell, i) { |
|
1385 | $.map(this.get_cells(), function (cell, i) { | |
1300 |
if (cell instanceof |
|
1386 | if (cell instanceof codecell.CodeCell) { | |
1301 | cell.clear_output(); |
|
1387 | cell.clear_output(); | |
1302 | } |
|
1388 | } | |
1303 | }); |
|
1389 | }); | |
@@ -1313,7 +1399,7 b' var IPython = (function (IPython) {' | |||||
1313 | Notebook.prototype.scroll_output = function (index) { |
|
1399 | Notebook.prototype.scroll_output = function (index) { | |
1314 | var i = this.index_or_selected(index); |
|
1400 | var i = this.index_or_selected(index); | |
1315 | var cell = this.get_cell(i); |
|
1401 | var cell = this.get_cell(i); | |
1316 |
if (cell !== null && (cell instanceof |
|
1402 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
1317 | cell.scroll_output(); |
|
1403 | cell.scroll_output(); | |
1318 | this.set_dirty(true); |
|
1404 | this.set_dirty(true); | |
1319 | } |
|
1405 | } | |
@@ -1326,7 +1412,7 b' var IPython = (function (IPython) {' | |||||
1326 | */ |
|
1412 | */ | |
1327 | Notebook.prototype.scroll_all_output = function () { |
|
1413 | Notebook.prototype.scroll_all_output = function () { | |
1328 | $.map(this.get_cells(), function (cell, i) { |
|
1414 | $.map(this.get_cells(), function (cell, i) { | |
1329 |
if (cell instanceof |
|
1415 | if (cell instanceof codecell.CodeCell) { | |
1330 | cell.scroll_output(); |
|
1416 | cell.scroll_output(); | |
1331 | } |
|
1417 | } | |
1332 | }); |
|
1418 | }); | |
@@ -1342,7 +1428,7 b' var IPython = (function (IPython) {' | |||||
1342 | Notebook.prototype.toggle_output = function (index) { |
|
1428 | Notebook.prototype.toggle_output = function (index) { | |
1343 | var i = this.index_or_selected(index); |
|
1429 | var i = this.index_or_selected(index); | |
1344 | var cell = this.get_cell(i); |
|
1430 | var cell = this.get_cell(i); | |
1345 |
if (cell !== null && (cell instanceof |
|
1431 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
1346 | cell.toggle_output(); |
|
1432 | cell.toggle_output(); | |
1347 | this.set_dirty(true); |
|
1433 | this.set_dirty(true); | |
1348 | } |
|
1434 | } | |
@@ -1355,7 +1441,7 b' var IPython = (function (IPython) {' | |||||
1355 | */ |
|
1441 | */ | |
1356 | Notebook.prototype.toggle_all_output = function () { |
|
1442 | Notebook.prototype.toggle_all_output = function () { | |
1357 | $.map(this.get_cells(), function (cell, i) { |
|
1443 | $.map(this.get_cells(), function (cell, i) { | |
1358 |
if (cell instanceof |
|
1444 | if (cell instanceof codecell.CodeCell) { | |
1359 | cell.toggle_output(); |
|
1445 | cell.toggle_output(); | |
1360 | } |
|
1446 | } | |
1361 | }); |
|
1447 | }); | |
@@ -1372,7 +1458,7 b' var IPython = (function (IPython) {' | |||||
1372 | Notebook.prototype.toggle_output_scroll = function (index) { |
|
1458 | Notebook.prototype.toggle_output_scroll = function (index) { | |
1373 | var i = this.index_or_selected(index); |
|
1459 | var i = this.index_or_selected(index); | |
1374 | var cell = this.get_cell(i); |
|
1460 | var cell = this.get_cell(i); | |
1375 |
if (cell !== null && (cell instanceof |
|
1461 | if (cell !== null && (cell instanceof codecell.CodeCell)) { | |
1376 | cell.toggle_output_scroll(); |
|
1462 | cell.toggle_output_scroll(); | |
1377 | this.set_dirty(true); |
|
1463 | this.set_dirty(true); | |
1378 | } |
|
1464 | } | |
@@ -1385,7 +1471,7 b' var IPython = (function (IPython) {' | |||||
1385 | */ |
|
1471 | */ | |
1386 | Notebook.prototype.toggle_all_output_scroll = function () { |
|
1472 | Notebook.prototype.toggle_all_output_scroll = function () { | |
1387 | $.map(this.get_cells(), function (cell, i) { |
|
1473 | $.map(this.get_cells(), function (cell, i) { | |
1388 |
if (cell instanceof |
|
1474 | if (cell instanceof codecell.CodeCell) { | |
1389 | cell.toggle_output_scroll(); |
|
1475 | cell.toggle_output_scroll(); | |
1390 | } |
|
1476 | } | |
1391 | }); |
|
1477 | }); | |
@@ -1403,6 +1489,34 b' var IPython = (function (IPython) {' | |||||
1403 | Notebook.prototype.cell_toggle_line_numbers = function() { |
|
1489 | Notebook.prototype.cell_toggle_line_numbers = function() { | |
1404 | this.get_selected_cell().toggle_line_numbers(); |
|
1490 | this.get_selected_cell().toggle_line_numbers(); | |
1405 | }; |
|
1491 | }; | |
|
1492 | ||||
|
1493 | /** | |||
|
1494 | * Set the codemirror mode for all code cells, including the default for | |||
|
1495 | * new code cells. | |||
|
1496 | * | |||
|
1497 | * @method set_codemirror_mode | |||
|
1498 | */ | |||
|
1499 | Notebook.prototype.set_codemirror_mode = function(newmode){ | |||
|
1500 | if (newmode === this.codemirror_mode) { | |||
|
1501 | return; | |||
|
1502 | } | |||
|
1503 | this.codemirror_mode = newmode; | |||
|
1504 | codecell.CodeCell.options_default.cm_config.mode = newmode; | |||
|
1505 | modename = newmode.name || newmode | |||
|
1506 | ||||
|
1507 | that = this; | |||
|
1508 | CodeMirror.requireMode(modename, function(){ | |||
|
1509 | $.map(that.get_cells(), function(cell, i) { | |||
|
1510 | if (cell.cell_type === 'code'){ | |||
|
1511 | cell.code_mirror.setOption('mode', newmode); | |||
|
1512 | // This is currently redundant, because cm_config ends up as | |||
|
1513 | // codemirror's own .options object, but I don't want to | |||
|
1514 | // rely on that. | |||
|
1515 | cell.cm_config.mode = newmode; | |||
|
1516 | } | |||
|
1517 | }); | |||
|
1518 | }) | |||
|
1519 | }; | |||
1406 |
|
1520 | |||
1407 | // Session related things |
|
1521 | // Session related things | |
1408 |
|
1522 | |||
@@ -1411,9 +1525,54 b' var IPython = (function (IPython) {' | |||||
1411 | * |
|
1525 | * | |
1412 | * @method start_session |
|
1526 | * @method start_session | |
1413 | */ |
|
1527 | */ | |
1414 | Notebook.prototype.start_session = function () { |
|
1528 | Notebook.prototype.start_session = function (kernel_name) { | |
1415 | this.session = new IPython.Session(this, this.options); |
|
1529 | var that = this; | |
1416 | this.session.start($.proxy(this._session_started, this)); |
|
1530 | if (kernel_name === undefined) { | |
|
1531 | kernel_name = this.default_kernel_name; | |||
|
1532 | } | |||
|
1533 | if (this._session_starting) { | |||
|
1534 | throw new session.SessionAlreadyStarting(); | |||
|
1535 | } | |||
|
1536 | this._session_starting = true; | |||
|
1537 | ||||
|
1538 | if (this.session !== null) { | |||
|
1539 | var s = this.session; | |||
|
1540 | this.session = null; | |||
|
1541 | // need to start the new session in a callback after delete, | |||
|
1542 | // because javascript does not guarantee the ordering of AJAX requests (?!) | |||
|
1543 | s.delete(function () { | |||
|
1544 | // on successful delete, start new session | |||
|
1545 | that._session_starting = false; | |||
|
1546 | that.start_session(kernel_name); | |||
|
1547 | }, function (jqXHR, status, error) { | |||
|
1548 | // log the failed delete, but still create a new session | |||
|
1549 | // 404 just means it was already deleted by someone else, | |||
|
1550 | // but other errors are possible. | |||
|
1551 | utils.log_ajax_error(jqXHR, status, error); | |||
|
1552 | that._session_starting = false; | |||
|
1553 | that.start_session(kernel_name); | |||
|
1554 | } | |||
|
1555 | ); | |||
|
1556 | return; | |||
|
1557 | } | |||
|
1558 | ||||
|
1559 | ||||
|
1560 | ||||
|
1561 | this.session = new session.Session({ | |||
|
1562 | base_url: this.base_url, | |||
|
1563 | ws_url: this.ws_url, | |||
|
1564 | notebook_path: this.notebook_path, | |||
|
1565 | notebook_name: this.notebook_name, | |||
|
1566 | // For now, create all sessions with the 'python' kernel, which is the | |||
|
1567 | // default. Later, the user will be able to select kernels. This is | |||
|
1568 | // overridden if KernelManager.kernel_cmd is specified for the server. | |||
|
1569 | kernel_name: kernel_name, | |||
|
1570 | notebook: this}); | |||
|
1571 | ||||
|
1572 | this.session.start( | |||
|
1573 | $.proxy(this._session_started, this), | |||
|
1574 | $.proxy(this._session_start_failed, this) | |||
|
1575 | ); | |||
1417 | }; |
|
1576 | }; | |
1418 |
|
1577 | |||
1419 |
|
1578 | |||
@@ -1422,17 +1581,22 b' var IPython = (function (IPython) {' | |||||
1422 | * comm manager to the widget manager |
|
1581 | * comm manager to the widget manager | |
1423 | * |
|
1582 | * | |
1424 | */ |
|
1583 | */ | |
1425 | Notebook.prototype._session_started = function(){ |
|
1584 | Notebook.prototype._session_started = function (){ | |
|
1585 | this._session_starting = false; | |||
1426 | this.kernel = this.session.kernel; |
|
1586 | this.kernel = this.session.kernel; | |
1427 | var ncells = this.ncells(); |
|
1587 | var ncells = this.ncells(); | |
1428 | for (var i=0; i<ncells; i++) { |
|
1588 | for (var i=0; i<ncells; i++) { | |
1429 | var cell = this.get_cell(i); |
|
1589 | var cell = this.get_cell(i); | |
1430 |
if (cell instanceof |
|
1590 | if (cell instanceof codecell.CodeCell) { | |
1431 | cell.set_kernel(this.session.kernel); |
|
1591 | cell.set_kernel(this.session.kernel); | |
1432 | } |
|
1592 | } | |
1433 | } |
|
1593 | } | |
1434 | }; |
|
1594 | }; | |
1435 |
|
1595 | Notebook.prototype._session_start_failed = function (jqxhr, status, error){ | ||
|
1596 | this._session_starting = false; | |||
|
1597 | utils.log_ajax_error(jqxhr, status, error); | |||
|
1598 | }; | |||
|
1599 | ||||
1436 | /** |
|
1600 | /** | |
1437 | * Prompt the user to restart the IPython kernel. |
|
1601 | * Prompt the user to restart the IPython kernel. | |
1438 | * |
|
1602 | * | |
@@ -1440,7 +1604,9 b' var IPython = (function (IPython) {' | |||||
1440 | */ |
|
1604 | */ | |
1441 | Notebook.prototype.restart_kernel = function () { |
|
1605 | Notebook.prototype.restart_kernel = function () { | |
1442 | var that = this; |
|
1606 | var that = this; | |
1443 |
|
|
1607 | dialog.modal({ | |
|
1608 | notebook: this, | |||
|
1609 | keyboard_manager: this.keyboard_manager, | |||
1444 | title : "Restart kernel or continue running?", |
|
1610 | title : "Restart kernel or continue running?", | |
1445 | body : $("<p/>").text( |
|
1611 | body : $("<p/>").text( | |
1446 | 'Do you want to restart the current kernel? You will lose all variables defined in it.' |
|
1612 | 'Do you want to restart the current kernel? You will lose all variables defined in it.' | |
@@ -1633,6 +1799,13 b' var IPython = (function (IPython) {' | |||||
1633 | this.metadata = content.metadata; |
|
1799 | this.metadata = content.metadata; | |
1634 | this.notebook_name = data.name; |
|
1800 | this.notebook_name = data.name; | |
1635 | var trusted = true; |
|
1801 | var trusted = true; | |
|
1802 | ||||
|
1803 | // Trigger an event changing the kernel spec - this will set the default | |||
|
1804 | // codemirror mode | |||
|
1805 | if (this.metadata.kernelspec !== undefined) { | |||
|
1806 | this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec); | |||
|
1807 | } | |||
|
1808 | ||||
1636 | // Only handle 1 worksheet for now. |
|
1809 | // Only handle 1 worksheet for now. | |
1637 | var worksheet = content.worksheets[0]; |
|
1810 | var worksheet = content.worksheets[0]; | |
1638 | if (worksheet !== undefined) { |
|
1811 | if (worksheet !== undefined) { | |
@@ -1660,10 +1833,12 b' var IPython = (function (IPython) {' | |||||
1660 | } |
|
1833 | } | |
1661 | if (trusted != this.trusted) { |
|
1834 | if (trusted != this.trusted) { | |
1662 | this.trusted = trusted; |
|
1835 | this.trusted = trusted; | |
1663 |
|
|
1836 | this.events.trigger("trust_changed.Notebook", trusted); | |
1664 | } |
|
1837 | } | |
1665 | if (content.worksheets.length > 1) { |
|
1838 | if (content.worksheets.length > 1) { | |
1666 |
|
|
1839 | dialog.modal({ | |
|
1840 | notebook: this, | |||
|
1841 | keyboard_manager: this.keyboard_manager, | |||
1667 | title : "Multiple worksheets", |
|
1842 | title : "Multiple worksheets", | |
1668 | body : "This notebook has " + data.worksheets.length + " worksheets, " + |
|
1843 | body : "This notebook has " + data.worksheets.length + " worksheets, " + | |
1669 | "but this version of IPython can only handle the first. " + |
|
1844 | "but this version of IPython can only handle the first. " + | |
@@ -1705,7 +1880,7 b' var IPython = (function (IPython) {' | |||||
1705 | }; |
|
1880 | }; | |
1706 | if (trusted != this.trusted) { |
|
1881 | if (trusted != this.trusted) { | |
1707 | this.trusted = trusted; |
|
1882 | this.trusted = trusted; | |
1708 |
|
|
1883 | this.events.trigger("trust_changed.Notebook", trusted); | |
1709 | } |
|
1884 | } | |
1710 | return data; |
|
1885 | return data; | |
1711 | }; |
|
1886 | }; | |
@@ -1730,10 +1905,10 b' var IPython = (function (IPython) {' | |||||
1730 | that.save_notebook(); |
|
1905 | that.save_notebook(); | |
1731 | } |
|
1906 | } | |
1732 | }, interval); |
|
1907 | }, interval); | |
1733 |
|
|
1908 | this.events.trigger("autosave_enabled.Notebook", interval); | |
1734 | } else { |
|
1909 | } else { | |
1735 | this.autosave_timer = null; |
|
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 | var model = {}; |
|
1923 | var model = {}; | |
1749 | model.name = this.notebook_name; |
|
1924 | model.name = this.notebook_name; | |
1750 | model.path = this.notebook_path; |
|
1925 | model.path = this.notebook_path; | |
|
1926 | model.type = 'notebook'; | |||
|
1927 | model.format = 'json'; | |||
1751 | model.content = this.toJSON(); |
|
1928 | model.content = this.toJSON(); | |
1752 | model.content.nbformat = this.nbformat; |
|
1929 | model.content.nbformat = this.nbformat; | |
1753 | model.content.nbformat_minor = this.nbformat_minor; |
|
1930 | model.content.nbformat_minor = this.nbformat_minor; | |
@@ -1768,10 +1945,10 b' var IPython = (function (IPython) {' | |||||
1768 | settings[key] = extra_settings[key]; |
|
1945 | settings[key] = extra_settings[key]; | |
1769 | } |
|
1946 | } | |
1770 | } |
|
1947 | } | |
1771 |
|
|
1948 | this.events.trigger('notebook_saving.Notebook'); | |
1772 | var url = utils.url_join_encode( |
|
1949 | var url = utils.url_join_encode( | |
1773 | this.base_url, |
|
1950 | this.base_url, | |
1774 |
'api/ |
|
1951 | 'api/contents', | |
1775 | this.notebook_path, |
|
1952 | this.notebook_path, | |
1776 | this.notebook_name |
|
1953 | this.notebook_name | |
1777 | ); |
|
1954 | ); | |
@@ -1789,7 +1966,7 b' var IPython = (function (IPython) {' | |||||
1789 | */ |
|
1966 | */ | |
1790 | Notebook.prototype.save_notebook_success = function (start, data, status, xhr) { |
|
1967 | Notebook.prototype.save_notebook_success = function (start, data, status, xhr) { | |
1791 | this.set_dirty(false); |
|
1968 | this.set_dirty(false); | |
1792 |
|
|
1969 | this.events.trigger('notebook_saved.Notebook'); | |
1793 | this._update_autosave_interval(start); |
|
1970 | this._update_autosave_interval(start); | |
1794 | if (this._checkpoint_after_save) { |
|
1971 | if (this._checkpoint_after_save) { | |
1795 | this.create_checkpoint(); |
|
1972 | this.create_checkpoint(); | |
@@ -1826,7 +2003,7 b' var IPython = (function (IPython) {' | |||||
1826 | * @param {String} error HTTP error message |
|
2003 | * @param {String} error HTTP error message | |
1827 | */ |
|
2004 | */ | |
1828 | Notebook.prototype.save_notebook_error = function (xhr, status, error) { |
|
2005 | Notebook.prototype.save_notebook_error = function (xhr, status, error) { | |
1829 |
|
|
2006 | this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]); | |
1830 | }; |
|
2007 | }; | |
1831 |
|
2008 | |||
1832 | /** |
|
2009 | /** | |
@@ -1851,7 +2028,9 b' var IPython = (function (IPython) {' | |||||
1851 | ); |
|
2028 | ); | |
1852 |
|
2029 | |||
1853 | var nb = this; |
|
2030 | var nb = this; | |
1854 |
|
|
2031 | dialog.modal({ | |
|
2032 | notebook: this, | |||
|
2033 | keyboard_manager: this.keyboard_manager, | |||
1855 | title: "Trust this notebook?", |
|
2034 | title: "Trust this notebook?", | |
1856 | body: body, |
|
2035 | body: body, | |
1857 |
|
2036 | |||
@@ -1867,7 +2046,7 b' var IPython = (function (IPython) {' | |||||
1867 | cell.output_area.trusted = true; |
|
2046 | cell.output_area.trusted = true; | |
1868 | } |
|
2047 | } | |
1869 | } |
|
2048 | } | |
1870 |
|
|
2049 | this.events.on('notebook_saved.Notebook', function () { | |
1871 | window.location.reload(); |
|
2050 | window.location.reload(); | |
1872 | }); |
|
2051 | }); | |
1873 | nb.save_notebook(); |
|
2052 | nb.save_notebook(); | |
@@ -1902,7 +2081,7 b' var IPython = (function (IPython) {' | |||||
1902 | }; |
|
2081 | }; | |
1903 | var url = utils.url_join_encode( |
|
2082 | var url = utils.url_join_encode( | |
1904 | base_url, |
|
2083 | base_url, | |
1905 |
'api/ |
|
2084 | 'api/contents', | |
1906 | path |
|
2085 | path | |
1907 | ); |
|
2086 | ); | |
1908 | $.ajax(url,settings); |
|
2087 | $.ajax(url,settings); | |
@@ -1931,7 +2110,7 b' var IPython = (function (IPython) {' | |||||
1931 | }; |
|
2110 | }; | |
1932 | var url = utils.url_join_encode( |
|
2111 | var url = utils.url_join_encode( | |
1933 | base_url, |
|
2112 | base_url, | |
1934 |
'api/ |
|
2113 | 'api/contents', | |
1935 | path |
|
2114 | path | |
1936 | ); |
|
2115 | ); | |
1937 | $.ajax(url,settings); |
|
2116 | $.ajax(url,settings); | |
@@ -1953,10 +2132,10 b' var IPython = (function (IPython) {' | |||||
1953 | success : $.proxy(that.rename_success, this), |
|
2132 | success : $.proxy(that.rename_success, this), | |
1954 | error : $.proxy(that.rename_error, this) |
|
2133 | error : $.proxy(that.rename_error, this) | |
1955 | }; |
|
2134 | }; | |
1956 |
|
|
2135 | this.events.trigger('rename_notebook.Notebook', data); | |
1957 | var url = utils.url_join_encode( |
|
2136 | var url = utils.url_join_encode( | |
1958 | this.base_url, |
|
2137 | this.base_url, | |
1959 |
'api/ |
|
2138 | 'api/contents', | |
1960 | this.notebook_path, |
|
2139 | this.notebook_path, | |
1961 | this.notebook_name |
|
2140 | this.notebook_name | |
1962 | ); |
|
2141 | ); | |
@@ -1974,7 +2153,7 b' var IPython = (function (IPython) {' | |||||
1974 | }; |
|
2153 | }; | |
1975 | var url = utils.url_join_encode( |
|
2154 | var url = utils.url_join_encode( | |
1976 | this.base_url, |
|
2155 | this.base_url, | |
1977 |
'api/ |
|
2156 | 'api/contents', | |
1978 | this.notebook_path, |
|
2157 | this.notebook_path, | |
1979 | this.notebook_name |
|
2158 | this.notebook_name | |
1980 | ); |
|
2159 | ); | |
@@ -1986,32 +2165,33 b' var IPython = (function (IPython) {' | |||||
1986 | var name = this.notebook_name = json.name; |
|
2165 | var name = this.notebook_name = json.name; | |
1987 | var path = json.path; |
|
2166 | var path = json.path; | |
1988 | this.session.rename_notebook(name, path); |
|
2167 | this.session.rename_notebook(name, path); | |
1989 |
|
|
2168 | this.events.trigger('notebook_renamed.Notebook', json); | |
1990 | }; |
|
2169 | }; | |
1991 |
|
2170 | |||
1992 | Notebook.prototype.rename_error = function (xhr, status, error) { |
|
2171 | Notebook.prototype.rename_error = function (xhr, status, error) { | |
1993 | var that = this; |
|
2172 | var that = this; | |
1994 | var dialog = $('<div/>').append( |
|
2173 | var dialog_body = $('<div/>').append( | |
1995 | $("<p/>").addClass("rename-message") |
|
2174 | $("<p/>").text('This notebook name already exists.') | |
1996 | .text('This notebook name already exists.') |
|
|||
1997 | ); |
|
2175 | ); | |
1998 |
|
|
2176 | this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]); | |
1999 |
|
|
2177 | dialog.modal({ | |
|
2178 | notebook: this, | |||
|
2179 | keyboard_manager: this.keyboard_manager, | |||
2000 | title: "Notebook Rename Error!", |
|
2180 | title: "Notebook Rename Error!", | |
2001 | body: dialog, |
|
2181 | body: dialog_body, | |
2002 | buttons : { |
|
2182 | buttons : { | |
2003 | "Cancel": {}, |
|
2183 | "Cancel": {}, | |
2004 | "OK": { |
|
2184 | "OK": { | |
2005 | class: "btn-primary", |
|
2185 | class: "btn-primary", | |
2006 | click: function () { |
|
2186 | click: function () { | |
2007 |
|
|
2187 | this.save_widget.rename_notebook({notebook:that}); | |
2008 | }} |
|
2188 | }} | |
2009 | }, |
|
2189 | }, | |
2010 | open : function (event, ui) { |
|
2190 | open : function (event, ui) { | |
2011 | var that = $(this); |
|
2191 | var that = $(this); | |
2012 | // Upon ENTER, click the OK button. |
|
2192 | // Upon ENTER, click the OK button. | |
2013 | that.find('input[type="text"]').keydown(function (event, ui) { |
|
2193 | that.find('input[type="text"]').keydown(function (event, ui) { | |
2014 |
if (event.which === |
|
2194 | if (event.which === this.keyboard.keycodes.enter) { | |
2015 | that.find('.btn-primary').first().click(); |
|
2195 | that.find('.btn-primary').first().click(); | |
2016 | } |
|
2196 | } | |
2017 | }); |
|
2197 | }); | |
@@ -2039,10 +2219,10 b' var IPython = (function (IPython) {' | |||||
2039 | success : $.proxy(this.load_notebook_success,this), |
|
2219 | success : $.proxy(this.load_notebook_success,this), | |
2040 | error : $.proxy(this.load_notebook_error,this), |
|
2220 | error : $.proxy(this.load_notebook_error,this), | |
2041 | }; |
|
2221 | }; | |
2042 |
|
|
2222 | this.events.trigger('notebook_loading.Notebook'); | |
2043 | var url = utils.url_join_encode( |
|
2223 | var url = utils.url_join_encode( | |
2044 | this.base_url, |
|
2224 | this.base_url, | |
2045 |
'api/ |
|
2225 | 'api/contents', | |
2046 | this.notebook_path, |
|
2226 | this.notebook_path, | |
2047 | this.notebook_name |
|
2227 | this.notebook_name | |
2048 | ); |
|
2228 | ); | |
@@ -2077,7 +2257,9 b' var IPython = (function (IPython) {' | |||||
2077 | "newer notebook format will be used and older versions of IPython " + |
|
2257 | "newer notebook format will be used and older versions of IPython " + | |
2078 | "may not be able to read it. To keep the older version, close the " + |
|
2258 | "may not be able to read it. To keep the older version, close the " + | |
2079 | "notebook without saving it."; |
|
2259 | "notebook without saving it."; | |
2080 |
|
|
2260 | dialog.modal({ | |
|
2261 | notebook: this, | |||
|
2262 | keyboard_manager: this.keyboard_manager, | |||
2081 | title : "Notebook converted", |
|
2263 | title : "Notebook converted", | |
2082 | body : msg, |
|
2264 | body : msg, | |
2083 | buttons : { |
|
2265 | buttons : { | |
@@ -2094,7 +2276,9 b' var IPython = (function (IPython) {' | |||||
2094 | this_vs + ". You can still work with this notebook, but some features " + |
|
2276 | this_vs + ". You can still work with this notebook, but some features " + | |
2095 | "introduced in later notebook versions may not be available."; |
|
2277 | "introduced in later notebook versions may not be available."; | |
2096 |
|
2278 | |||
2097 |
|
|
2279 | dialog.modal({ | |
|
2280 | notebook: this, | |||
|
2281 | keyboard_manager: this.keyboard_manager, | |||
2098 | title : "Newer Notebook", |
|
2282 | title : "Newer Notebook", | |
2099 | body : msg, |
|
2283 | body : msg, | |
2100 | buttons : { |
|
2284 | buttons : { | |
@@ -2109,22 +2293,25 b' var IPython = (function (IPython) {' | |||||
2109 | // Create the session after the notebook is completely loaded to prevent |
|
2293 | // Create the session after the notebook is completely loaded to prevent | |
2110 | // code execution upon loading, which is a security risk. |
|
2294 | // code execution upon loading, which is a security risk. | |
2111 | if (this.session === null) { |
|
2295 | if (this.session === null) { | |
2112 | this.start_session(); |
|
2296 | var kernelspec = this.metadata.kernelspec || {}; | |
|
2297 | var kernel_name = kernelspec.name || this.default_kernel_name; | |||
|
2298 | ||||
|
2299 | this.start_session(kernel_name); | |||
2113 | } |
|
2300 | } | |
2114 | // load our checkpoint list |
|
2301 | // load our checkpoint list | |
2115 | this.list_checkpoints(); |
|
2302 | this.list_checkpoints(); | |
2116 |
|
2303 | |||
2117 | // load toolbar state |
|
2304 | // load toolbar state | |
2118 | if (this.metadata.celltoolbar) { |
|
2305 | if (this.metadata.celltoolbar) { | |
2119 |
|
|
2306 | celltoolbar.CellToolbar.global_show(); | |
2120 |
|
|
2307 | celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar); | |
2121 | } else { |
|
2308 | } else { | |
2122 |
|
|
2309 | celltoolbar.CellToolbar.global_hide(); | |
2123 | } |
|
2310 | } | |
2124 |
|
2311 | |||
2125 | // now that we're fully loaded, it is safe to restore save functionality |
|
2312 | // now that we're fully loaded, it is safe to restore save functionality | |
2126 | delete(this.save_notebook); |
|
2313 | delete(this.save_notebook); | |
2127 |
|
|
2314 | this.events.trigger('notebook_loaded.Notebook'); | |
2128 | }; |
|
2315 | }; | |
2129 |
|
2316 | |||
2130 | /** |
|
2317 | /** | |
@@ -2136,16 +2323,19 b' var IPython = (function (IPython) {' | |||||
2136 | * @param {String} error HTTP error message |
|
2323 | * @param {String} error HTTP error message | |
2137 | */ |
|
2324 | */ | |
2138 | Notebook.prototype.load_notebook_error = function (xhr, status, error) { |
|
2325 | Notebook.prototype.load_notebook_error = function (xhr, status, error) { | |
2139 |
|
|
2326 | this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]); | |
|
2327 | utils.log_ajax_error(xhr, status, error); | |||
2140 | var msg; |
|
2328 | var msg; | |
2141 | if (xhr.status === 400) { |
|
2329 | if (xhr.status === 400) { | |
2142 | msg = error; |
|
2330 | msg = escape(utils.ajax_error_msg(xhr)); | |
2143 | } else if (xhr.status === 500) { |
|
2331 | } else if (xhr.status === 500) { | |
2144 | msg = "An unknown error occurred while loading this notebook. " + |
|
2332 | msg = "An unknown error occurred while loading this notebook. " + | |
2145 | "This version can load notebook formats " + |
|
2333 | "This version can load notebook formats " + | |
2146 | "v" + this.nbformat + " or earlier."; |
|
2334 | "v" + this.nbformat + " or earlier. See the server log for details."; | |
2147 | } |
|
2335 | } | |
2148 |
|
|
2336 | dialog.modal({ | |
|
2337 | notebook: this, | |||
|
2338 | keyboard_manager: this.keyboard_manager, | |||
2149 | title: "Error loading notebook", |
|
2339 | title: "Error loading notebook", | |
2150 | body : msg, |
|
2340 | body : msg, | |
2151 | buttons : { |
|
2341 | buttons : { | |
@@ -2196,7 +2386,7 b' var IPython = (function (IPython) {' | |||||
2196 | Notebook.prototype.list_checkpoints = function () { |
|
2386 | Notebook.prototype.list_checkpoints = function () { | |
2197 | var url = utils.url_join_encode( |
|
2387 | var url = utils.url_join_encode( | |
2198 | this.base_url, |
|
2388 | this.base_url, | |
2199 |
'api/ |
|
2389 | 'api/contents', | |
2200 | this.notebook_path, |
|
2390 | this.notebook_path, | |
2201 | this.notebook_name, |
|
2391 | this.notebook_name, | |
2202 | 'checkpoints' |
|
2392 | 'checkpoints' | |
@@ -2224,7 +2414,7 b' var IPython = (function (IPython) {' | |||||
2224 | } else { |
|
2414 | } else { | |
2225 | this.last_checkpoint = null; |
|
2415 | this.last_checkpoint = null; | |
2226 | } |
|
2416 | } | |
2227 |
|
|
2417 | this.events.trigger('checkpoints_listed.Notebook', [data]); | |
2228 | }; |
|
2418 | }; | |
2229 |
|
2419 | |||
2230 | /** |
|
2420 | /** | |
@@ -2236,7 +2426,7 b' var IPython = (function (IPython) {' | |||||
2236 | * @param {String} error_msg HTTP error message |
|
2426 | * @param {String} error_msg HTTP error message | |
2237 | */ |
|
2427 | */ | |
2238 | Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) { |
|
2428 | Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) { | |
2239 |
|
|
2429 | this.events.trigger('list_checkpoints_failed.Notebook'); | |
2240 | }; |
|
2430 | }; | |
2241 |
|
2431 | |||
2242 | /** |
|
2432 | /** | |
@@ -2247,7 +2437,7 b' var IPython = (function (IPython) {' | |||||
2247 | Notebook.prototype.create_checkpoint = function () { |
|
2437 | Notebook.prototype.create_checkpoint = function () { | |
2248 | var url = utils.url_join_encode( |
|
2438 | var url = utils.url_join_encode( | |
2249 | this.base_url, |
|
2439 | this.base_url, | |
2250 |
'api/ |
|
2440 | 'api/contents', | |
2251 | this.notebook_path, |
|
2441 | this.notebook_path, | |
2252 | this.notebook_name, |
|
2442 | this.notebook_name, | |
2253 | 'checkpoints' |
|
2443 | 'checkpoints' | |
@@ -2270,7 +2460,7 b' var IPython = (function (IPython) {' | |||||
2270 | Notebook.prototype.create_checkpoint_success = function (data, status, xhr) { |
|
2460 | Notebook.prototype.create_checkpoint_success = function (data, status, xhr) { | |
2271 | data = $.parseJSON(data); |
|
2461 | data = $.parseJSON(data); | |
2272 | this.add_checkpoint(data); |
|
2462 | this.add_checkpoint(data); | |
2273 |
|
|
2463 | this.events.trigger('checkpoint_created.Notebook', data); | |
2274 | }; |
|
2464 | }; | |
2275 |
|
2465 | |||
2276 | /** |
|
2466 | /** | |
@@ -2282,7 +2472,7 b' var IPython = (function (IPython) {' | |||||
2282 | * @param {String} error_msg HTTP error message |
|
2472 | * @param {String} error_msg HTTP error message | |
2283 | */ |
|
2473 | */ | |
2284 | Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) { |
|
2474 | Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) { | |
2285 |
|
|
2475 | this.events.trigger('checkpoint_failed.Notebook'); | |
2286 | }; |
|
2476 | }; | |
2287 |
|
2477 | |||
2288 | Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) { |
|
2478 | Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) { | |
@@ -2309,7 +2499,9 b' var IPython = (function (IPython) {' | |||||
2309 | ).css("text-align", "center") |
|
2499 | ).css("text-align", "center") | |
2310 | ); |
|
2500 | ); | |
2311 |
|
2501 | |||
2312 |
|
|
2502 | dialog.modal({ | |
|
2503 | notebook: this, | |||
|
2504 | keyboard_manager: this.keyboard_manager, | |||
2313 | title : "Revert notebook to checkpoint", |
|
2505 | title : "Revert notebook to checkpoint", | |
2314 | body : body, |
|
2506 | body : body, | |
2315 | buttons : { |
|
2507 | buttons : { | |
@@ -2331,10 +2523,10 b' var IPython = (function (IPython) {' | |||||
2331 | * @param {String} checkpoint ID |
|
2523 | * @param {String} checkpoint ID | |
2332 | */ |
|
2524 | */ | |
2333 | Notebook.prototype.restore_checkpoint = function (checkpoint) { |
|
2525 | Notebook.prototype.restore_checkpoint = function (checkpoint) { | |
2334 |
|
|
2526 | this.events.trigger('notebook_restoring.Notebook', checkpoint); | |
2335 | var url = utils.url_join_encode( |
|
2527 | var url = utils.url_join_encode( | |
2336 | this.base_url, |
|
2528 | this.base_url, | |
2337 |
'api/ |
|
2529 | 'api/contents', | |
2338 | this.notebook_path, |
|
2530 | this.notebook_path, | |
2339 | this.notebook_name, |
|
2531 | this.notebook_name, | |
2340 | 'checkpoints', |
|
2532 | 'checkpoints', | |
@@ -2356,7 +2548,7 b' var IPython = (function (IPython) {' | |||||
2356 | * @param {jqXHR} xhr jQuery Ajax object |
|
2548 | * @param {jqXHR} xhr jQuery Ajax object | |
2357 | */ |
|
2549 | */ | |
2358 | Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) { |
|
2550 | Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) { | |
2359 |
|
|
2551 | this.events.trigger('checkpoint_restored.Notebook'); | |
2360 | this.load_notebook(this.notebook_name, this.notebook_path); |
|
2552 | this.load_notebook(this.notebook_name, this.notebook_path); | |
2361 | }; |
|
2553 | }; | |
2362 |
|
2554 | |||
@@ -2369,7 +2561,7 b' var IPython = (function (IPython) {' | |||||
2369 | * @param {String} error_msg HTTP error message |
|
2561 | * @param {String} error_msg HTTP error message | |
2370 | */ |
|
2562 | */ | |
2371 | Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) { |
|
2563 | Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) { | |
2372 |
|
|
2564 | this.events.trigger('checkpoint_restore_failed.Notebook'); | |
2373 | }; |
|
2565 | }; | |
2374 |
|
2566 | |||
2375 | /** |
|
2567 | /** | |
@@ -2379,10 +2571,10 b' var IPython = (function (IPython) {' | |||||
2379 | * @param {String} checkpoint ID |
|
2571 | * @param {String} checkpoint ID | |
2380 | */ |
|
2572 | */ | |
2381 | Notebook.prototype.delete_checkpoint = function (checkpoint) { |
|
2573 | Notebook.prototype.delete_checkpoint = function (checkpoint) { | |
2382 |
|
|
2574 | this.events.trigger('notebook_restoring.Notebook', checkpoint); | |
2383 | var url = utils.url_join_encode( |
|
2575 | var url = utils.url_join_encode( | |
2384 | this.base_url, |
|
2576 | this.base_url, | |
2385 |
'api/ |
|
2577 | 'api/contents', | |
2386 | this.notebook_path, |
|
2578 | this.notebook_path, | |
2387 | this.notebook_name, |
|
2579 | this.notebook_name, | |
2388 | 'checkpoints', |
|
2580 | 'checkpoints', | |
@@ -2404,7 +2596,7 b' var IPython = (function (IPython) {' | |||||
2404 | * @param {jqXHR} xhr jQuery Ajax object |
|
2596 | * @param {jqXHR} xhr jQuery Ajax object | |
2405 | */ |
|
2597 | */ | |
2406 | Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) { |
|
2598 | Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) { | |
2407 |
|
|
2599 | this.events.trigger('checkpoint_deleted.Notebook', data); | |
2408 | this.load_notebook(this.notebook_name, this.notebook_path); |
|
2600 | this.load_notebook(this.notebook_name, this.notebook_path); | |
2409 | }; |
|
2601 | }; | |
2410 |
|
2602 | |||
@@ -2414,17 +2606,15 b' var IPython = (function (IPython) {' | |||||
2414 | * @method delete_checkpoint_error |
|
2606 | * @method delete_checkpoint_error | |
2415 | * @param {jqXHR} xhr jQuery Ajax object |
|
2607 | * @param {jqXHR} xhr jQuery Ajax object | |
2416 | * @param {String} status Description of response status |
|
2608 | * @param {String} status Description of response status | |
2417 |
* @param {String} error |
|
2609 | * @param {String} error HTTP error message | |
2418 | */ |
|
2610 | */ | |
2419 |
Notebook.prototype.delete_checkpoint_error = function (xhr, status, error |
|
2611 | Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) { | |
2420 |
|
|
2612 | this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]); | |
2421 | }; |
|
2613 | }; | |
2422 |
|
2614 | |||
2423 |
|
2615 | |||
|
2616 | // For backwards compatability. | |||
2424 | IPython.Notebook = Notebook; |
|
2617 | IPython.Notebook = Notebook; | |
2425 |
|
2618 | |||
2426 |
|
2619 | return {'Notebook': Notebook}; | ||
2427 | return IPython; |
|
2620 | }); | |
2428 |
|
||||
2429 | }(IPython)); |
|
|||
2430 |
|
@@ -1,21 +1,31 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2012 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
||||
8 | //============================================================================ |
|
|||
9 | // Notification widget |
|
|||
10 | //============================================================================ |
|
|||
11 |
|
||||
12 | var IPython = (function (IPython) { |
|
|||
13 | "use strict"; |
|
|||
14 | var utils = IPython.utils; |
|
|||
15 |
|
3 | |||
|
4 | define([ | |||
|
5 | 'base/js/namespace', | |||
|
6 | 'jquery', | |||
|
7 | 'base/js/utils', | |||
|
8 | 'base/js/dialog', | |||
|
9 | 'notebook/js/notificationwidget', | |||
|
10 | 'moment' | |||
|
11 | ], function(IPython, $, utils, dialog, notificationwidget, moment) { | |||
|
12 | "use strict"; | |||
16 |
|
13 | |||
17 | var NotificationArea = function (selector) { |
|
14 | var NotificationArea = function (selector, options) { | |
|
15 | // Constructor | |||
|
16 | // | |||
|
17 | // Parameters: | |||
|
18 | // selector: string | |||
|
19 | // options: dictionary | |||
|
20 | // Dictionary of keyword arguments. | |||
|
21 | // notebook: Notebook instance | |||
|
22 | // events: $(Events) instance | |||
|
23 | // save_widget: SaveWidget instance | |||
18 | this.selector = selector; |
|
24 | this.selector = selector; | |
|
25 | this.events = options.events; | |||
|
26 | this.save_widget = options.save_widget; | |||
|
27 | this.notebook = options.notebook; | |||
|
28 | this.keyboard_manager = options.keyboard_manager; | |||
19 | if (this.selector !== undefined) { |
|
29 | if (this.selector !== undefined) { | |
20 | this.element = $(selector); |
|
30 | this.element = $(selector); | |
21 | } |
|
31 | } | |
@@ -23,13 +33,10 b' var IPython = (function (IPython) {' | |||||
23 | }; |
|
33 | }; | |
24 |
|
34 | |||
25 | NotificationArea.prototype.temp_message = function (msg, timeout, css_class) { |
|
35 | NotificationArea.prototype.temp_message = function (msg, timeout, css_class) { | |
26 | var uuid = utils.uuid(); |
|
|||
27 | if( css_class == 'danger') {css_class = 'ui-state-error';} |
|
36 | if( css_class == 'danger') {css_class = 'ui-state-error';} | |
28 | if( css_class == 'warning') {css_class = 'ui-state-highlight';} |
|
37 | if( css_class == 'warning') {css_class = 'ui-state-highlight';} | |
29 | var tdiv = $('<div>') |
|
38 | var tdiv = $('<div>') | |
30 | .attr('id',uuid) |
|
39 | .addClass('notification_widget') | |
31 | .addClass('notification_widget ui-widget ui-widget-content ui-corner-all') |
|
|||
32 | .addClass('border-box-sizing') |
|
|||
33 | .addClass(css_class) |
|
40 | .addClass(css_class) | |
34 | .hide() |
|
41 | .hide() | |
35 | .text(msg); |
|
42 | .text(msg); | |
@@ -63,46 +70,53 b' var IPython = (function (IPython) {' | |||||
63 | } |
|
70 | } | |
64 | var div = $('<div/>').attr('id','notification_'+name); |
|
71 | var div = $('<div/>').attr('id','notification_'+name); | |
65 | $(this.selector).append(div); |
|
72 | $(this.selector).append(div); | |
66 |
this.widget_dict[name] = new |
|
73 | this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name); | |
67 | return this.widget_dict[name]; |
|
74 | return this.widget_dict[name]; | |
68 | }; |
|
75 | }; | |
69 |
|
76 | |||
70 | NotificationArea.prototype.init_notification_widgets = function() { |
|
77 | NotificationArea.prototype.init_notification_widgets = function() { | |
|
78 | var that = this; | |||
71 | var knw = this.new_notification_widget('kernel'); |
|
79 | var knw = this.new_notification_widget('kernel'); | |
72 | var $kernel_ind_icon = $("#kernel_indicator_icon"); |
|
80 | var $kernel_ind_icon = $("#kernel_indicator_icon"); | |
73 | var $modal_ind_icon = $("#modal_indicator_icon"); |
|
81 | var $modal_ind_icon = $("#modal_indicator_icon"); | |
74 |
|
82 | |||
75 | // Command/Edit mode |
|
83 | // Command/Edit mode | |
76 |
|
|
84 | this.events.on('edit_mode.Notebook',function () { | |
77 |
|
|
85 | that.save_widget.update_document_title(); | |
78 | $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode'); |
|
86 | $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode'); | |
79 | }); |
|
87 | }); | |
80 |
|
88 | |||
81 |
|
|
89 | this.events.on('command_mode.Notebook',function () { | |
82 |
|
|
90 | that.save_widget.update_document_title(); | |
83 | $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode'); |
|
91 | $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode'); | |
84 | }); |
|
92 | }); | |
85 |
|
93 | |||
86 | // Implicitly start off in Command mode, switching to Edit mode will trigger event |
|
94 | // Implicitly start off in Command mode, switching to Edit mode will trigger event | |
87 |
$modal_ind_icon.attr('class','command |
|
95 | $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode'); | |
88 |
|
96 | |||
89 | // Kernel events |
|
97 | // Kernel events | |
90 |
|
|
98 | this.events.on('status_idle.Kernel',function () { | |
91 |
|
|
99 | that.save_widget.update_document_title(); | |
92 | $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle'); |
|
100 | $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle'); | |
93 | }); |
|
101 | }); | |
94 |
|
102 | |||
95 |
|
|
103 | this.events.on('status_busy.Kernel',function () { | |
96 | window.document.title='(Busy) '+window.document.title; |
|
104 | window.document.title='(Busy) '+window.document.title; | |
97 | $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy'); |
|
105 | $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy'); | |
98 | }); |
|
106 | }); | |
99 |
|
107 | |||
100 |
|
|
108 | this.events.on('status_restarting.Kernel',function () { | |
101 |
|
|
109 | that.save_widget.update_document_title(); | |
102 | knw.set_message("Restarting kernel", 2000); |
|
110 | knw.set_message("Restarting kernel", 2000); | |
103 | }); |
|
111 | }); | |
104 |
|
112 | |||
105 |
|
|
113 | this.events.on('status_dead.Kernel',function () { | |
|
114 | that.save_widget.update_document_title(); | |||
|
115 | knw.danger("Dead kernel"); | |||
|
116 | $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead'); | |||
|
117 | }); | |||
|
118 | ||||
|
119 | this.events.on('status_interrupting.Kernel',function () { | |||
106 | knw.set_message("Interrupting kernel", 2000); |
|
120 | knw.set_message("Interrupting kernel", 2000); | |
107 | }); |
|
121 | }); | |
108 |
|
122 | |||
@@ -110,28 +124,32 b' var IPython = (function (IPython) {' | |||||
110 | // When the kernel_info reply arrives, the kernel is idle. |
|
124 | // When the kernel_info reply arrives, the kernel is idle. | |
111 | $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy'); |
|
125 | $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy'); | |
112 |
|
126 | |||
113 |
|
|
127 | this.events.on('status_started.Kernel', function (evt, data) { | |
|
128 | knw.info("Websockets Connected", 500); | |||
|
129 | that.events.trigger('status_busy.Kernel'); | |||
114 | data.kernel.kernel_info(function () { |
|
130 | data.kernel.kernel_info(function () { | |
115 |
|
|
131 | that.events.trigger('status_idle.Kernel'); | |
116 | }); |
|
132 | }); | |
117 | }); |
|
133 | }); | |
118 |
|
134 | |||
119 |
|
|
135 | this.events.on('status_dead.Kernel',function () { | |
120 | var msg = 'The kernel has died, and the automatic restart has failed.' + |
|
136 | var msg = 'The kernel has died, and the automatic restart has failed.' + | |
121 | ' It is possible the kernel cannot be restarted.' + |
|
137 | ' It is possible the kernel cannot be restarted.' + | |
122 | ' If you are not able to restart the kernel, you will still be able to save' + |
|
138 | ' If you are not able to restart the kernel, you will still be able to save' + | |
123 | ' the notebook, but running code will no longer work until the notebook' + |
|
139 | ' the notebook, but running code will no longer work until the notebook' + | |
124 | ' is reopened.'; |
|
140 | ' is reopened.'; | |
125 |
|
141 | |||
126 |
|
|
142 | dialog.modal({ | |
127 | title: "Dead kernel", |
|
143 | title: "Dead kernel", | |
128 | body : msg, |
|
144 | body : msg, | |
|
145 | keyboard_manager: that.keyboard_manager, | |||
|
146 | notebook: that.notebook, | |||
129 | buttons : { |
|
147 | buttons : { | |
130 | "Manual Restart": { |
|
148 | "Manual Restart": { | |
131 | class: "btn-danger", |
|
149 | class: "btn-danger", | |
132 | click: function () { |
|
150 | click: function () { | |
133 |
|
|
151 | that.events.trigger('status_restarting.Kernel'); | |
134 |
|
|
152 | that.notebook.start_kernel(); | |
135 | } |
|
153 | } | |
136 | }, |
|
154 | }, | |
137 | "Don't restart": {} |
|
155 | "Don't restart": {} | |
@@ -139,13 +157,18 b' var IPython = (function (IPython) {' | |||||
139 | }); |
|
157 | }); | |
140 | }); |
|
158 | }); | |
141 |
|
159 | |||
142 |
|
|
160 | this.events.on('websocket_closed.Kernel', function (event, data) { | |
143 | var kernel = data.kernel; |
|
161 | var kernel = data.kernel; | |
144 | var ws_url = data.ws_url; |
|
162 | var ws_url = data.ws_url; | |
145 | var early = data.early; |
|
163 | var early = data.early; | |
146 | var msg; |
|
164 | var msg; | |
|
165 | ||||
|
166 | $kernel_ind_icon | |||
|
167 | .attr('class', 'kernel_disconnected_icon') | |||
|
168 | .attr('title', 'No Connection to Kernel'); | |||
|
169 | ||||
147 | if (!early) { |
|
170 | if (!early) { | |
148 |
knw. |
|
171 | knw.warning('Reconnecting'); | |
149 | setTimeout(function () { |
|
172 | setTimeout(function () { | |
150 | kernel.start_channels(); |
|
173 | kernel.start_channels(); | |
151 | }, 5000); |
|
174 | }, 5000); | |
@@ -155,14 +178,16 b' var IPython = (function (IPython) {' | |||||
155 | msg = "A WebSocket connection could not be established." + |
|
178 | msg = "A WebSocket connection could not be established." + | |
156 | " You will NOT be able to run code. Check your" + |
|
179 | " You will NOT be able to run code. Check your" + | |
157 | " network connection or notebook server configuration."; |
|
180 | " network connection or notebook server configuration."; | |
158 |
|
|
181 | dialog.modal({ | |
159 | title: "WebSocket connection failed", |
|
182 | title: "WebSocket connection failed", | |
160 | body: msg, |
|
183 | body: msg, | |
|
184 | keyboard_manager: that.keyboard_manager, | |||
|
185 | notebook: that.notebook, | |||
161 | buttons : { |
|
186 | buttons : { | |
162 | "OK": {}, |
|
187 | "OK": {}, | |
163 | "Reconnect": { |
|
188 | "Reconnect": { | |
164 | click: function () { |
|
189 | click: function () { | |
165 |
knw. |
|
190 | knw.warning('Reconnecting'); | |
166 | setTimeout(function () { |
|
191 | setTimeout(function () { | |
167 | kernel.start_channels(); |
|
192 | kernel.start_channels(); | |
168 | }, 5000); |
|
193 | }, 5000); | |
@@ -176,52 +201,52 b' var IPython = (function (IPython) {' | |||||
176 | var nnw = this.new_notification_widget('notebook'); |
|
201 | var nnw = this.new_notification_widget('notebook'); | |
177 |
|
202 | |||
178 | // Notebook events |
|
203 | // Notebook events | |
179 |
|
|
204 | this.events.on('notebook_loading.Notebook', function () { | |
180 | nnw.set_message("Loading notebook",500); |
|
205 | nnw.set_message("Loading notebook",500); | |
181 | }); |
|
206 | }); | |
182 |
|
|
207 | this.events.on('notebook_loaded.Notebook', function () { | |
183 | nnw.set_message("Notebook loaded",500); |
|
208 | nnw.set_message("Notebook loaded",500); | |
184 | }); |
|
209 | }); | |
185 |
|
|
210 | this.events.on('notebook_saving.Notebook', function () { | |
186 | nnw.set_message("Saving notebook",500); |
|
211 | nnw.set_message("Saving notebook",500); | |
187 | }); |
|
212 | }); | |
188 |
|
|
213 | this.events.on('notebook_saved.Notebook', function () { | |
189 | nnw.set_message("Notebook saved",2000); |
|
214 | nnw.set_message("Notebook saved",2000); | |
190 | }); |
|
215 | }); | |
191 |
|
|
216 | this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) { | |
192 |
nnw. |
|
217 | nnw.warning(data || "Notebook save failed"); | |
193 | }); |
|
218 | }); | |
194 |
|
219 | |||
195 | // Checkpoint events |
|
220 | // Checkpoint events | |
196 |
|
|
221 | this.events.on('checkpoint_created.Notebook', function (evt, data) { | |
197 | var msg = "Checkpoint created"; |
|
222 | var msg = "Checkpoint created"; | |
198 | if (data.last_modified) { |
|
223 | if (data.last_modified) { | |
199 | var d = new Date(data.last_modified); |
|
224 | var d = new Date(data.last_modified); | |
200 |
msg = msg + ": " + d.format("HH: |
|
225 | msg = msg + ": " + moment(d).format("HH:mm:ss"); | |
201 | } |
|
226 | } | |
202 | nnw.set_message(msg, 2000); |
|
227 | nnw.set_message(msg, 2000); | |
203 | }); |
|
228 | }); | |
204 |
|
|
229 | this.events.on('checkpoint_failed.Notebook', function () { | |
205 |
nnw. |
|
230 | nnw.warning("Checkpoint failed"); | |
206 | }); |
|
231 | }); | |
207 |
|
|
232 | this.events.on('checkpoint_deleted.Notebook', function () { | |
208 | nnw.set_message("Checkpoint deleted", 500); |
|
233 | nnw.set_message("Checkpoint deleted", 500); | |
209 | }); |
|
234 | }); | |
210 |
|
|
235 | this.events.on('checkpoint_delete_failed.Notebook', function () { | |
211 |
nnw. |
|
236 | nnw.warning("Checkpoint delete failed"); | |
212 | }); |
|
237 | }); | |
213 |
|
|
238 | this.events.on('checkpoint_restoring.Notebook', function () { | |
214 | nnw.set_message("Restoring to checkpoint...", 500); |
|
239 | nnw.set_message("Restoring to checkpoint...", 500); | |
215 | }); |
|
240 | }); | |
216 |
|
|
241 | this.events.on('checkpoint_restore_failed.Notebook', function () { | |
217 |
nnw. |
|
242 | nnw.warning("Checkpoint restore failed"); | |
218 | }); |
|
243 | }); | |
219 |
|
244 | |||
220 | // Autosave events |
|
245 | // Autosave events | |
221 |
|
|
246 | this.events.on('autosave_disabled.Notebook', function () { | |
222 | nnw.set_message("Autosave disabled", 2000); |
|
247 | nnw.set_message("Autosave disabled", 2000); | |
223 | }); |
|
248 | }); | |
224 |
|
|
249 | this.events.on('autosave_enabled.Notebook', function (evt, interval) { | |
225 | nnw.set_message("Saving every " + interval / 1000 + "s", 1000); |
|
250 | nnw.set_message("Saving every " + interval / 1000 + "s", 1000); | |
226 | }); |
|
251 | }); | |
227 |
|
252 | |||
@@ -229,7 +254,5 b' var IPython = (function (IPython) {' | |||||
229 |
|
254 | |||
230 | IPython.NotificationArea = NotificationArea; |
|
255 | IPython.NotificationArea = NotificationArea; | |
231 |
|
256 | |||
232 | return IPython; |
|
257 | return {'NotificationArea': NotificationArea}; | |
233 |
|
258 | }); | ||
234 | }(IPython)); |
|
|||
235 |
|
@@ -1,18 +1,11 b'' | |||||
1 | //---------------------------------------------------------------------------- |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Copyright (C) 2008-2011 The IPython Development Team |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 | // |
|
|||
4 | // Distributed under the terms of the BSD License. The full license is in |
|
|||
5 | // the file COPYING, distributed as part of this software. |
|
|||
6 | //---------------------------------------------------------------------------- |
|
|||
7 |
|
3 | |||
8 | //============================================================================ |
|
4 | define([ | |
9 | // Notification widget |
|
5 | 'base/js/namespace', | |
10 | //============================================================================ |
|
6 | 'jquery', | |
11 |
|
7 | ], function(IPython, $) { | ||
12 | var IPython = (function (IPython) { |
|
|||
13 | "use strict"; |
|
8 | "use strict"; | |
14 | var utils = IPython.utils; |
|
|||
15 |
|
||||
16 |
|
9 | |||
17 | var NotificationWidget = function (selector) { |
|
10 | var NotificationWidget = function (selector) { | |
18 | this.selector = selector; |
|
11 | this.selector = selector; | |
@@ -22,7 +15,6 b' var IPython = (function (IPython) {' | |||||
22 | this.element = $(selector); |
|
15 | this.element = $(selector); | |
23 | this.style(); |
|
16 | this.style(); | |
24 | } |
|
17 | } | |
25 | this.element.button(); |
|
|||
26 | this.element.hide(); |
|
18 | this.element.hide(); | |
27 | var that = this; |
|
19 | var that = this; | |
28 |
|
20 | |||
@@ -31,10 +23,8 b' var IPython = (function (IPython) {' | |||||
31 |
|
23 | |||
32 | }; |
|
24 | }; | |
33 |
|
25 | |||
34 |
|
||||
35 | NotificationWidget.prototype.style = function () { |
|
26 | NotificationWidget.prototype.style = function () { | |
36 |
this.element.addClass('notification_widget |
|
27 | this.element.addClass('notification_widget'); | |
37 | this.element.addClass('border-box-sizing'); |
|
|||
38 | }; |
|
28 | }; | |
39 |
|
29 | |||
40 | // msg : message to display |
|
30 | // msg : message to display | |
@@ -43,14 +33,24 b' var IPython = (function (IPython) {' | |||||
43 | // if timeout <= 0 |
|
33 | // if timeout <= 0 | |
44 | // click_callback : function called if user click on notification |
|
34 | // click_callback : function called if user click on notification | |
45 | // could return false to prevent the notification to be dismissed |
|
35 | // could return false to prevent the notification to be dismissed | |
46 | NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, opts) { |
|
36 | NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) { | |
47 | var opts = opts || {}; |
|
37 | var options = options || {}; | |
48 |
var callback = click_callback || function() {return |
|
38 | var callback = click_callback || function() {return true;}; | |
49 | var that = this; |
|
39 | var that = this; | |
50 | this.inner.attr('class', opts.icon); |
|
40 | // unbind potential previous callback | |
51 | this.inner.attr('title', opts.title); |
|
41 | this.element.unbind('click'); | |
|
42 | this.inner.attr('class', options.icon); | |||
|
43 | this.inner.attr('title', options.title); | |||
52 | this.inner.text(msg); |
|
44 | this.inner.text(msg); | |
53 | this.element.fadeIn(100); |
|
45 | this.element.fadeIn(100); | |
|
46 | ||||
|
47 | // reset previous set style | |||
|
48 | this.element.removeClass(); | |||
|
49 | this.style(); | |||
|
50 | if (options.class){ | |||
|
51 | ||||
|
52 | this.element.addClass(options.class) | |||
|
53 | } | |||
54 | if (this.timeout !== null) { |
|
54 | if (this.timeout !== null) { | |
55 | clearTimeout(this.timeout); |
|
55 | clearTimeout(this.timeout); | |
56 | this.timeout = null; |
|
56 | this.timeout = null; | |
@@ -62,7 +62,7 b' var IPython = (function (IPython) {' | |||||
62 | }, timeout); |
|
62 | }, timeout); | |
63 | } else { |
|
63 | } else { | |
64 | this.element.click(function() { |
|
64 | this.element.click(function() { | |
65 | if( callback() != false ) { |
|
65 | if( callback() !== false ) { | |
66 | that.element.fadeOut(100, function () {that.inner.text('');}); |
|
66 | that.element.fadeOut(100, function () {that.inner.text('');}); | |
67 | that.element.unbind('click'); |
|
67 | that.element.unbind('click'); | |
68 | } |
|
68 | } | |
@@ -75,14 +75,30 b' var IPython = (function (IPython) {' | |||||
75 | }; |
|
75 | }; | |
76 |
|
76 | |||
77 |
|
77 | |||
|
78 | NotificationWidget.prototype.info = function (msg, timeout, click_callback, options) { | |||
|
79 | var options = options || {}; | |||
|
80 | options.class = options.class +' info'; | |||
|
81 | var timeout = timeout || 3500; | |||
|
82 | this.set_message(msg, timeout, click_callback, options); | |||
|
83 | } | |||
|
84 | NotificationWidget.prototype.warning = function (msg, timeout, click_callback, options) { | |||
|
85 | var options = options || {}; | |||
|
86 | options.class = options.class +' warning'; | |||
|
87 | this.set_message(msg, timeout, click_callback, options); | |||
|
88 | } | |||
|
89 | NotificationWidget.prototype.danger = function (msg, timeout, click_callback, options) { | |||
|
90 | var options = options || {}; | |||
|
91 | options.class = options.class +' danger'; | |||
|
92 | this.set_message(msg, timeout, click_callback, options); | |||
|
93 | } | |||
|
94 | ||||
|
95 | ||||
78 | NotificationWidget.prototype.get_message = function () { |
|
96 | NotificationWidget.prototype.get_message = function () { | |
79 | return this.inner.html(); |
|
97 | return this.inner.html(); | |
80 | }; |
|
98 | }; | |
81 |
|
99 | |||
82 |
|
100 | // For backwards compatibility. | ||
83 | IPython.NotificationWidget = NotificationWidget; |
|
101 | IPython.NotificationWidget = NotificationWidget; | |
84 |
|
102 | |||
85 | return IPython; |
|
103 | return {'NotificationWidget': NotificationWidget}; | |
86 |
|
104 | }); | ||
87 | }(IPython)); |
|
|||
88 |
|
@@ -1,38 +1,37 b'' | |||||
1 | // Copyright (c) IPython Development Team. |
|
1 | // Copyright (c) IPython Development Team. | |
2 | // Distributed under the terms of the Modified BSD License. |
|
2 | // Distributed under the terms of the Modified BSD License. | |
3 |
|
3 | |||
4 | //============================================================================ |
|
4 | define([ | |
5 | // OutputArea |
|
5 | 'base/js/namespace', | |
6 | //============================================================================ |
|
6 | 'jqueryui', | |
7 |
|
7 | 'base/js/utils', | ||
8 | /** |
|
8 | 'base/js/security', | |
9 | * @module IPython |
|
9 | 'base/js/keyboard', | |
10 | * @namespace IPython |
|
10 | 'notebook/js/mathjaxutils', | |
11 | * @submodule OutputArea |
|
11 | 'components/marked/lib/marked', | |
12 | */ |
|
12 | ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) { | |
13 | var IPython = (function (IPython) { |
|
|||
14 | "use strict"; |
|
13 | "use strict"; | |
15 |
|
14 | |||
16 | var utils = IPython.utils; |
|
|||
17 |
|
||||
18 | /** |
|
15 | /** | |
19 | * @class OutputArea |
|
16 | * @class OutputArea | |
20 | * |
|
17 | * | |
21 | * @constructor |
|
18 | * @constructor | |
22 | */ |
|
19 | */ | |
23 |
|
20 | |||
24 |
var OutputArea = function ( |
|
21 | var OutputArea = function (options) { | |
25 | this.selector = selector; |
|
22 | this.selector = options.selector; | |
26 |
this. |
|
23 | this.events = options.events; | |
|
24 | this.keyboard_manager = options.keyboard_manager; | |||
|
25 | this.wrapper = $(options.selector); | |||
27 | this.outputs = []; |
|
26 | this.outputs = []; | |
28 | this.collapsed = false; |
|
27 | this.collapsed = false; | |
29 | this.scrolled = false; |
|
28 | this.scrolled = false; | |
30 | this.trusted = true; |
|
29 | this.trusted = true; | |
31 | this.clear_queued = null; |
|
30 | this.clear_queued = null; | |
32 | if (prompt_area === undefined) { |
|
31 | if (options.prompt_area === undefined) { | |
33 | this.prompt_area = true; |
|
32 | this.prompt_area = true; | |
34 | } else { |
|
33 | } else { | |
35 | this.prompt_area = prompt_area; |
|
34 | this.prompt_area = options.prompt_area; | |
36 | } |
|
35 | } | |
37 | this.create_elements(); |
|
36 | this.create_elements(); | |
38 | this.style(); |
|
37 | this.style(); | |
@@ -101,7 +100,7 b' var IPython = (function (IPython) {' | |||||
101 |
|
100 | |||
102 | this.element.resize(function () { |
|
101 | this.element.resize(function () { | |
103 | // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled |
|
102 | // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled | |
104 |
if ( |
|
103 | if ( utils.browser[0] === "Firefox" ) { | |
105 | return; |
|
104 | return; | |
106 | } |
|
105 | } | |
107 | // maybe scroll output, |
|
106 | // maybe scroll output, | |
@@ -282,12 +281,15 b' var IPython = (function (IPython) {' | |||||
282 | needs_height_reset = true; |
|
281 | needs_height_reset = true; | |
283 | } |
|
282 | } | |
284 |
|
283 | |||
|
284 | var record_output = true; | |||
|
285 | ||||
285 | if (json.output_type === 'execute_result') { |
|
286 | if (json.output_type === 'execute_result') { | |
286 | this.append_execute_result(json); |
|
287 | this.append_execute_result(json); | |
287 | } else if (json.output_type === 'error') { |
|
288 | } else if (json.output_type === 'error') { | |
288 | this.append_error(json); |
|
289 | this.append_error(json); | |
289 | } else if (json.output_type === 'stream') { |
|
290 | } else if (json.output_type === 'stream') { | |
290 | this.append_stream(json); |
|
291 | // append_stream might have merged the output with earlier stream output | |
|
292 | record_output = this.append_stream(json); | |||
291 | } |
|
293 | } | |
292 |
|
294 | |||
293 | // We must release the animation fixed height in a callback since Gecko |
|
295 | // We must release the animation fixed height in a callback since Gecko | |
@@ -308,7 +310,9 b' var IPython = (function (IPython) {' | |||||
308 | handle_appended(); |
|
310 | handle_appended(); | |
309 | } |
|
311 | } | |
310 |
|
312 | |||
311 | this.outputs.push(json); |
|
313 | if (record_output) { | |
|
314 | this.outputs.push(json); | |||
|
315 | } | |||
312 | }; |
|
316 | }; | |
313 |
|
317 | |||
314 |
|
318 | |||
@@ -459,20 +463,23 b' var IPython = (function (IPython) {' | |||||
459 | // latest output was in the same stream, |
|
463 | // latest output was in the same stream, | |
460 | // so append directly into its pre tag |
|
464 | // so append directly into its pre tag | |
461 | // escape ANSI & HTML specials: |
|
465 | // escape ANSI & HTML specials: | |
|
466 | last.text = utils.fixCarriageReturn(last.text + json.text); | |||
462 | var pre = this.element.find('div.'+subclass).last().find('pre'); |
|
467 | var pre = this.element.find('div.'+subclass).last().find('pre'); | |
463 |
var html = utils.fixC |
|
468 | var html = utils.fixConsole(last.text); | |
464 | pre.html() + utils.fixConsole(text)); |
|
|||
465 | // The only user content injected with this HTML call is |
|
469 | // The only user content injected with this HTML call is | |
466 | // escaped by the fixConsole() method. |
|
470 | // escaped by the fixConsole() method. | |
467 | pre.html(html); |
|
471 | pre.html(html); | |
468 | return; |
|
472 | // return false signals that we merged this output with the previous one, | |
|
473 | // and the new output shouldn't be recorded. | |||
|
474 | return false; | |||
469 | } |
|
475 | } | |
470 | } |
|
476 | } | |
471 |
|
477 | |||
472 | if (!text.replace("\r", "")) { |
|
478 | if (!text.replace("\r", "")) { | |
473 | // text is nothing (empty string, \r, etc.) |
|
479 | // text is nothing (empty string, \r, etc.) | |
474 | // so don't append any elements, which might add undesirable space |
|
480 | // so don't append any elements, which might add undesirable space | |
475 | return; |
|
481 | // return true to indicate the output should be recorded. | |
|
482 | return true; | |||
476 | } |
|
483 | } | |
477 |
|
484 | |||
478 | // If we got here, attach a new div |
|
485 | // If we got here, attach a new div | |
@@ -482,6 +489,7 b' var IPython = (function (IPython) {' | |||||
482 | append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass); |
|
489 | append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass); | |
483 | } |
|
490 | } | |
484 | this._safe_append(toinsert); |
|
491 | this._safe_append(toinsert); | |
|
492 | return true; | |||
485 | }; |
|
493 | }; | |
486 |
|
494 | |||
487 |
|
495 | |||
@@ -515,7 +523,7 b' var IPython = (function (IPython) {' | |||||
515 | if (!this.trusted && !OutputArea.safe_outputs[type]) { |
|
523 | if (!this.trusted && !OutputArea.safe_outputs[type]) { | |
516 | // not trusted, sanitize HTML |
|
524 | // not trusted, sanitize HTML | |
517 | if (type==='text/html' || type==='text/svg') { |
|
525 | if (type==='text/html' || type==='text/svg') { | |
518 |
value = |
|
526 | value = security.sanitize_html(value); | |
519 | } else { |
|
527 | } else { | |
520 | // don't display if we don't know how to sanitize it |
|
528 | // don't display if we don't know how to sanitize it | |
521 | console.log("Ignoring untrusted " + type + " output."); |
|
529 | console.log("Ignoring untrusted " + type + " output."); | |
@@ -531,7 +539,7 b' var IPython = (function (IPython) {' | |||||
531 | if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) { |
|
539 | if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) { | |
532 | setTimeout(handle_inserted, 0); |
|
540 | setTimeout(handle_inserted, 0); | |
533 | } |
|
541 | } | |
534 |
|
|
542 | this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]); | |
535 | return toinsert; |
|
543 | return toinsert; | |
536 | } |
|
544 | } | |
537 | } |
|
545 | } | |
@@ -542,7 +550,7 b' var IPython = (function (IPython) {' | |||||
542 | var append_html = function (html, md, element) { |
|
550 | var append_html = function (html, md, element) { | |
543 | var type = 'text/html'; |
|
551 | var type = 'text/html'; | |
544 | var toinsert = this.create_output_subarea(md, "output_html rendered_html", type); |
|
552 | var toinsert = this.create_output_subarea(md, "output_html rendered_html", type); | |
545 |
|
|
553 | this.keyboard_manager.register_events(toinsert); | |
546 | toinsert.append(html); |
|
554 | toinsert.append(html); | |
547 | element.append(toinsert); |
|
555 | element.append(toinsert); | |
548 | return toinsert; |
|
556 | return toinsert; | |
@@ -552,11 +560,11 b' var IPython = (function (IPython) {' | |||||
552 | var append_markdown = function(markdown, md, element) { |
|
560 | var append_markdown = function(markdown, md, element) { | |
553 | var type = 'text/markdown'; |
|
561 | var type = 'text/markdown'; | |
554 | var toinsert = this.create_output_subarea(md, "output_markdown", type); |
|
562 | var toinsert = this.create_output_subarea(md, "output_markdown", type); | |
555 |
var text_and_math = |
|
563 | var text_and_math = mathjaxutils.remove_math(markdown); | |
556 | var text = text_and_math[0]; |
|
564 | var text = text_and_math[0]; | |
557 | var math = text_and_math[1]; |
|
565 | var math = text_and_math[1]; | |
558 | var html = marked.parser(marked.lexer(text)); |
|
566 | var html = marked.parser(marked.lexer(text)); | |
559 |
html = |
|
567 | html = mathjaxutils.replace_math(html, math); | |
560 | toinsert.append(html); |
|
568 | toinsert.append(html); | |
561 | element.append(toinsert); |
|
569 | element.append(toinsert); | |
562 | return toinsert; |
|
570 | return toinsert; | |
@@ -567,13 +575,8 b' var IPython = (function (IPython) {' | |||||
567 | // We just eval the JS code, element appears in the local scope. |
|
575 | // We just eval the JS code, element appears in the local scope. | |
568 | var type = 'application/javascript'; |
|
576 | var type = 'application/javascript'; | |
569 | var toinsert = this.create_output_subarea(md, "output_javascript", type); |
|
577 | var toinsert = this.create_output_subarea(md, "output_javascript", type); | |
570 |
|
|
578 | this.keyboard_manager.register_events(toinsert); | |
571 | element.append(toinsert); |
|
579 | element.append(toinsert); | |
572 | // FIXME TODO : remove `container element for 3.0` |
|
|||
573 | //backward compat, js should be eval'ed in a context where `container` is defined. |
|
|||
574 | var container = element; |
|
|||
575 | container.show = function(){console.log('Warning "container.show()" is deprecated.')}; |
|
|||
576 | // end backward compat |
|
|||
577 |
|
580 | |||
578 | // Fix for ipython/issues/5293, make sure `element` is the area which |
|
581 | // Fix for ipython/issues/5293, make sure `element` is the area which | |
579 | // output can be inserted into at the time of JS execution. |
|
582 | // output can be inserted into at the time of JS execution. | |
@@ -763,7 +766,7 b' var IPython = (function (IPython) {' | |||||
763 | .keydown(function (event, ui) { |
|
766 | .keydown(function (event, ui) { | |
764 | // make sure we submit on enter, |
|
767 | // make sure we submit on enter, | |
765 | // and don't re-execute the *cell* on shift-enter |
|
768 | // and don't re-execute the *cell* on shift-enter | |
766 |
if (event.which === |
|
769 | if (event.which === keyboard.keycodes.enter) { | |
767 | that._submit_raw_input(); |
|
770 | that._submit_raw_input(); | |
768 | return false; |
|
771 | return false; | |
769 | } |
|
772 | } | |
@@ -775,7 +778,7 b' var IPython = (function (IPython) {' | |||||
775 | var raw_input = area.find('input.raw_input'); |
|
778 | var raw_input = area.find('input.raw_input'); | |
776 | // Register events that enable/disable the keyboard manager while raw |
|
779 | // Register events that enable/disable the keyboard manager while raw | |
777 | // input is focused. |
|
780 | // input is focused. | |
778 |
|
|
781 | this.keyboard_manager.register_events(raw_input); | |
779 | // Note, the following line used to read raw_input.focus().focus(). |
|
782 | // Note, the following line used to read raw_input.focus().focus(). | |
780 | // This seemed to be needed otherwise only the cell would be focused. |
|
783 | // This seemed to be needed otherwise only the cell would be focused. | |
781 | // But with the modal UI, this seems to work fine with one call to focus(). |
|
784 | // But with the modal UI, this seems to work fine with one call to focus(). | |
@@ -794,14 +797,14 b' var IPython = (function (IPython) {' | |||||
794 | } |
|
797 | } | |
795 | var content = { |
|
798 | var content = { | |
796 | output_type : 'stream', |
|
799 | output_type : 'stream', | |
797 |
|
|
800 | stream : 'stdout', | |
798 | text : theprompt.text() + echo + '\n' |
|
801 | text : theprompt.text() + echo + '\n' | |
799 | } |
|
802 | } | |
800 | // remove form container |
|
803 | // remove form container | |
801 | container.parent().remove(); |
|
804 | container.parent().remove(); | |
802 | // replace with plaintext version in stdout |
|
805 | // replace with plaintext version in stdout | |
803 | this.append_output(content, false); |
|
806 | this.append_output(content, false); | |
804 |
|
|
807 | this.events.trigger('send_input_reply.Kernel', value); | |
805 | } |
|
808 | } | |
806 |
|
809 | |||
807 |
|
810 | |||
@@ -992,8 +995,8 b' var IPython = (function (IPython) {' | |||||
992 | "application/pdf" : append_pdf |
|
995 | "application/pdf" : append_pdf | |
993 | }; |
|
996 | }; | |
994 |
|
997 | |||
|
998 | // For backwards compatability. | |||
995 | IPython.OutputArea = OutputArea; |
|
999 | IPython.OutputArea = OutputArea; | |
996 |
|
1000 | |||
997 | return IPython; |
|
1001 | return {'OutputArea': OutputArea}; | |
998 |
|
1002 | }); | ||
999 | }(IPython)); |
|
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file renamed from IPython/html/static/widgets/js/widget_container.js to IPython/html/static/widgets/js/widget_box.js |
|
NO CONTENT: file renamed from IPython/html/static/widgets/js/widget_container.js to IPython/html/static/widgets/js/widget_box.js | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file renamed from IPython/html/tests/widgets/widget_container.js to IPython/html/tests/widgets/widget_box.js |
|
NO CONTENT: file renamed from IPython/html/tests/widgets/widget_container.js to IPython/html/tests/widgets/widget_box.js | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file chmod 100755 => 100644 |
|
NO CONTENT: modified file chmod 100755 => 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file renamed from examples/Notebook/Animations Using clear_output.ipynb to examples/IPython Kernel/Animations Using clear_output.ipynb |
|
NO CONTENT: file renamed from examples/Notebook/Animations Using clear_output.ipynb to examples/IPython Kernel/Animations Using clear_output.ipynb |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file renamed from examples/Notebook/Plotting with Matplotlib.ipynb to examples/IPython Kernel/Plotting in the Notebook.ipynb |
|
NO CONTENT: file renamed from examples/Notebook/Plotting with Matplotlib.ipynb to examples/IPython Kernel/Plotting in the Notebook.ipynb |
1 | NO CONTENT: file renamed from examples/Notebook/Raw Input.ipynb to examples/IPython Kernel/Raw Input in the Notebook.ipynb |
|
NO CONTENT: file renamed from examples/Notebook/Raw Input.ipynb to examples/IPython Kernel/Raw Input in the Notebook.ipynb | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file renamed from examples/Notebook/Display System.ipynb to examples/IPython Kernel/Rich Output.ipynb |
|
NO CONTENT: file renamed from examples/Notebook/Display System.ipynb to examples/IPython Kernel/Rich Output.ipynb | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file renamed from examples/Notebook/SymPy.ipynb to examples/IPython Kernel/SymPy.ipynb |
|
NO CONTENT: file renamed from examples/Notebook/SymPy.ipynb to examples/IPython Kernel/SymPy.ipynb |
1 | NO CONTENT: file renamed from examples/Interactive Widgets/Widget Styles.ipynb to examples/IPython Kernel/Terminal Usage.ipynb |
|
NO CONTENT: file renamed from examples/Interactive Widgets/Widget Styles.ipynb to examples/IPython Kernel/Terminal Usage.ipynb | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file renamed from examples/Notebook/Trapezoid Rule.ipynb to examples/IPython Kernel/Trapezoid Rule.ipynb |
|
NO CONTENT: file renamed from examples/Notebook/Trapezoid Rule.ipynb to examples/IPython Kernel/Trapezoid Rule.ipynb |
1 | NO CONTENT: file renamed from examples/Interactive Widgets/Custom Widgets.ipynb to examples/Interactive Widgets/Date Picker Widget.ipynb |
|
NO CONTENT: file renamed from examples/Interactive Widgets/Custom Widgets.ipynb to examples/Interactive Widgets/Date Picker Widget.ipynb |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file renamed from examples/Notebook/User Interface.ipynb to examples/Notebook/Running the Notebook Server.ipynb |
|
NO CONTENT: file renamed from examples/Notebook/User Interface.ipynb to examples/Notebook/Running the Notebook Server.ipynb | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file renamed from examples/Notebook/Typesetting Math Using MathJax.ipynb to examples/Notebook/Typesetting Equations.ipynb |
|
NO CONTENT: file renamed from examples/Notebook/Typesetting Math Using MathJax.ipynb to examples/Notebook/Typesetting Equations.ipynb |
1 | NO CONTENT: file renamed from examples/Notebook/Markdown Cells.ipynb to examples/Notebook/Working With Markdown Cells.ipynb |
|
NO CONTENT: file renamed from examples/Notebook/Markdown Cells.ipynb to examples/Notebook/Working With Markdown Cells.ipynb | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file renamed from examples/Notebook/images/animation.m4v to examples/images/animation.m4v |
|
NO CONTENT: file renamed from examples/Notebook/images/animation.m4v to examples/images/animation.m4v |
1 | NO CONTENT: file renamed from examples/Notebook/images/python_logo.svg to examples/images/python_logo.svg |
|
NO CONTENT: file renamed from examples/Notebook/images/python_logo.svg to examples/images/python_logo.svg |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
General Comments 0
You need to be logged in to leave comments.
Login now