##// END OF EJS Templates
Merge remote-tracking branch 'upstream/master'
Doug Blank -
r15247:13bba28f merge
parent child Browse files
Show More

The requested changes are 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,701 +1,772 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Top-level display functions for displaying object in different formats.
2 """Top-level display functions for displaying object in different formats.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from __future__ import print_function
20 from __future__ import print_function
21
21
22 import os
22 import os
23 import struct
23 import struct
24
24
25 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
25 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
26 unicode_type)
26 unicode_type)
27
27 from IPython.testing.skipdoctest import skip_doctest
28 from .displaypub import publish_display_data
28 from .displaypub import publish_display_data
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # utility functions
31 # utility functions
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 def _safe_exists(path):
34 def _safe_exists(path):
35 """Check path, but don't let exceptions raise"""
35 """Check path, but don't let exceptions raise"""
36 try:
36 try:
37 return os.path.exists(path)
37 return os.path.exists(path)
38 except Exception:
38 except Exception:
39 return False
39 return False
40
40
41 def _merge(d1, d2):
41 def _merge(d1, d2):
42 """Like update, but merges sub-dicts instead of clobbering at the top level.
42 """Like update, but merges sub-dicts instead of clobbering at the top level.
43
43
44 Updates d1 in-place
44 Updates d1 in-place
45 """
45 """
46
46
47 if not isinstance(d2, dict) or not isinstance(d1, dict):
47 if not isinstance(d2, dict) or not isinstance(d1, dict):
48 return d2
48 return d2
49 for key, value in d2.items():
49 for key, value in d2.items():
50 d1[key] = _merge(d1.get(key), value)
50 d1[key] = _merge(d1.get(key), value)
51 return d1
51 return d1
52
52
53 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
53 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
54 """internal implementation of all display_foo methods
54 """internal implementation of all display_foo methods
55
55
56 Parameters
56 Parameters
57 ----------
57 ----------
58 mimetype : str
58 mimetype : str
59 The mimetype to be published (e.g. 'image/png')
59 The mimetype to be published (e.g. 'image/png')
60 objs : tuple of objects
60 objs : tuple of objects
61 The Python objects to display, or if raw=True raw text data to
61 The Python objects to display, or if raw=True raw text data to
62 display.
62 display.
63 raw : bool
63 raw : bool
64 Are the data objects raw data or Python objects that need to be
64 Are the data objects raw data or Python objects that need to be
65 formatted before display? [default: False]
65 formatted before display? [default: False]
66 metadata : dict (optional)
66 metadata : dict (optional)
67 Metadata to be associated with the specific mimetype output.
67 Metadata to be associated with the specific mimetype output.
68 """
68 """
69 if metadata:
69 if metadata:
70 metadata = {mimetype: metadata}
70 metadata = {mimetype: metadata}
71 if raw:
71 if raw:
72 # turn list of pngdata into list of { 'image/png': pngdata }
72 # turn list of pngdata into list of { 'image/png': pngdata }
73 objs = [ {mimetype: obj} for obj in objs ]
73 objs = [ {mimetype: obj} for obj in objs ]
74 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
74 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
75
75
76 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
77 # Main functions
77 # Main functions
78 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
79
79
80 def display(*objs, **kwargs):
80 def display(*objs, **kwargs):
81 """Display a Python object in all frontends.
81 """Display a Python object in all frontends.
82
82
83 By default all representations will be computed and sent to the frontends.
83 By default all representations will be computed and sent to the frontends.
84 Frontends can decide which representation is used and how.
84 Frontends can decide which representation is used and how.
85
85
86 Parameters
86 Parameters
87 ----------
87 ----------
88 objs : tuple of objects
88 objs : tuple of objects
89 The Python objects to display.
89 The Python objects to display.
90 raw : bool, optional
90 raw : bool, optional
91 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
91 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
92 or Python objects that need to be formatted before display? [default: False]
92 or Python objects that need to be formatted before display? [default: False]
93 include : list or tuple, optional
93 include : list or tuple, optional
94 A list of format type strings (MIME types) to include in the
94 A list of format type strings (MIME types) to include in the
95 format data dict. If this is set *only* the format types included
95 format data dict. If this is set *only* the format types included
96 in this list will be computed.
96 in this list will be computed.
97 exclude : list or tuple, optional
97 exclude : list or tuple, optional
98 A list of format type strings (MIME types) to exclude in the format
98 A list of format type strings (MIME types) to exclude in the format
99 data dict. If this is set all format types will be computed,
99 data dict. If this is set all format types will be computed,
100 except for those included in this argument.
100 except for those included in this argument.
101 metadata : dict, optional
101 metadata : dict, optional
102 A dictionary of metadata to associate with the output.
102 A dictionary of metadata to associate with the output.
103 mime-type keys in this dictionary will be associated with the individual
103 mime-type keys in this dictionary will be associated with the individual
104 representation formats, if they exist.
104 representation formats, if they exist.
105 """
105 """
106 raw = kwargs.get('raw', False)
106 raw = kwargs.get('raw', False)
107 include = kwargs.get('include')
107 include = kwargs.get('include')
108 exclude = kwargs.get('exclude')
108 exclude = kwargs.get('exclude')
109 metadata = kwargs.get('metadata')
109 metadata = kwargs.get('metadata')
110
110
111 from IPython.core.interactiveshell import InteractiveShell
111 from IPython.core.interactiveshell import InteractiveShell
112
112
113 if not raw:
113 if not raw:
114 format = InteractiveShell.instance().display_formatter.format
114 format = InteractiveShell.instance().display_formatter.format
115
115
116 for obj in objs:
116 for obj in objs:
117
117
118 # If _ipython_display_ is defined, use that to display this object.
118 # If _ipython_display_ is defined, use that to display this object.
119 display_method = getattr(obj, '_ipython_display_', None)
119 display_method = getattr(obj, '_ipython_display_', None)
120 if display_method is not None:
120 if display_method is not None:
121 try:
121 try:
122 display_method(**kwargs)
122 display_method(**kwargs)
123 except NotImplementedError:
123 except NotImplementedError:
124 pass
124 pass
125 else:
125 else:
126 continue
126 continue
127 if raw:
127 if raw:
128 publish_display_data('display', obj, metadata)
128 publish_display_data('display', obj, metadata)
129 else:
129 else:
130 format_dict, md_dict = format(obj, include=include, exclude=exclude)
130 format_dict, md_dict = format(obj, include=include, exclude=exclude)
131 if metadata:
131 if metadata:
132 # kwarg-specified metadata gets precedence
132 # kwarg-specified metadata gets precedence
133 _merge(md_dict, metadata)
133 _merge(md_dict, metadata)
134 publish_display_data('display', format_dict, md_dict)
134 publish_display_data('display', format_dict, md_dict)
135
135
136
136
137 def display_pretty(*objs, **kwargs):
137 def display_pretty(*objs, **kwargs):
138 """Display the pretty (default) representation of an object.
138 """Display the pretty (default) representation of an object.
139
139
140 Parameters
140 Parameters
141 ----------
141 ----------
142 objs : tuple of objects
142 objs : tuple of objects
143 The Python objects to display, or if raw=True raw text data to
143 The Python objects to display, or if raw=True raw text data to
144 display.
144 display.
145 raw : bool
145 raw : bool
146 Are the data objects raw data or Python objects that need to be
146 Are the data objects raw data or Python objects that need to be
147 formatted before display? [default: False]
147 formatted before display? [default: False]
148 metadata : dict (optional)
148 metadata : dict (optional)
149 Metadata to be associated with the specific mimetype output.
149 Metadata to be associated with the specific mimetype output.
150 """
150 """
151 _display_mimetype('text/plain', objs, **kwargs)
151 _display_mimetype('text/plain', objs, **kwargs)
152
152
153
153
154 def display_html(*objs, **kwargs):
154 def display_html(*objs, **kwargs):
155 """Display the HTML representation of an object.
155 """Display the HTML representation of an object.
156
156
157 Parameters
157 Parameters
158 ----------
158 ----------
159 objs : tuple of objects
159 objs : tuple of objects
160 The Python objects to display, or if raw=True raw HTML data to
160 The Python objects to display, or if raw=True raw HTML data to
161 display.
161 display.
162 raw : bool
162 raw : bool
163 Are the data objects raw data or Python objects that need to be
163 Are the data objects raw data or Python objects that need to be
164 formatted before display? [default: False]
164 formatted before display? [default: False]
165 metadata : dict (optional)
165 metadata : dict (optional)
166 Metadata to be associated with the specific mimetype output.
166 Metadata to be associated with the specific mimetype output.
167 """
167 """
168 _display_mimetype('text/html', objs, **kwargs)
168 _display_mimetype('text/html', objs, **kwargs)
169
169
170
170
171 def display_svg(*objs, **kwargs):
171 def display_svg(*objs, **kwargs):
172 """Display the SVG representation of an object.
172 """Display the SVG representation of an object.
173
173
174 Parameters
174 Parameters
175 ----------
175 ----------
176 objs : tuple of objects
176 objs : tuple of objects
177 The Python objects to display, or if raw=True raw svg data to
177 The Python objects to display, or if raw=True raw svg data to
178 display.
178 display.
179 raw : bool
179 raw : bool
180 Are the data objects raw data or Python objects that need to be
180 Are the data objects raw data or Python objects that need to be
181 formatted before display? [default: False]
181 formatted before display? [default: False]
182 metadata : dict (optional)
182 metadata : dict (optional)
183 Metadata to be associated with the specific mimetype output.
183 Metadata to be associated with the specific mimetype output.
184 """
184 """
185 _display_mimetype('image/svg+xml', objs, **kwargs)
185 _display_mimetype('image/svg+xml', objs, **kwargs)
186
186
187
187
188 def display_png(*objs, **kwargs):
188 def display_png(*objs, **kwargs):
189 """Display the PNG representation of an object.
189 """Display the PNG representation of an object.
190
190
191 Parameters
191 Parameters
192 ----------
192 ----------
193 objs : tuple of objects
193 objs : tuple of objects
194 The Python objects to display, or if raw=True raw png data to
194 The Python objects to display, or if raw=True raw png data to
195 display.
195 display.
196 raw : bool
196 raw : bool
197 Are the data objects raw data or Python objects that need to be
197 Are the data objects raw data or Python objects that need to be
198 formatted before display? [default: False]
198 formatted before display? [default: False]
199 metadata : dict (optional)
199 metadata : dict (optional)
200 Metadata to be associated with the specific mimetype output.
200 Metadata to be associated with the specific mimetype output.
201 """
201 """
202 _display_mimetype('image/png', objs, **kwargs)
202 _display_mimetype('image/png', objs, **kwargs)
203
203
204
204
205 def display_jpeg(*objs, **kwargs):
205 def display_jpeg(*objs, **kwargs):
206 """Display the JPEG representation of an object.
206 """Display the JPEG representation of an object.
207
207
208 Parameters
208 Parameters
209 ----------
209 ----------
210 objs : tuple of objects
210 objs : tuple of objects
211 The Python objects to display, or if raw=True raw JPEG data to
211 The Python objects to display, or if raw=True raw JPEG data to
212 display.
212 display.
213 raw : bool
213 raw : bool
214 Are the data objects raw data or Python objects that need to be
214 Are the data objects raw data or Python objects that need to be
215 formatted before display? [default: False]
215 formatted before display? [default: False]
216 metadata : dict (optional)
216 metadata : dict (optional)
217 Metadata to be associated with the specific mimetype output.
217 Metadata to be associated with the specific mimetype output.
218 """
218 """
219 _display_mimetype('image/jpeg', objs, **kwargs)
219 _display_mimetype('image/jpeg', objs, **kwargs)
220
220
221
221
222 def display_latex(*objs, **kwargs):
222 def display_latex(*objs, **kwargs):
223 """Display the LaTeX representation of an object.
223 """Display the LaTeX representation of an object.
224
224
225 Parameters
225 Parameters
226 ----------
226 ----------
227 objs : tuple of objects
227 objs : tuple of objects
228 The Python objects to display, or if raw=True raw latex data to
228 The Python objects to display, or if raw=True raw latex data to
229 display.
229 display.
230 raw : bool
230 raw : bool
231 Are the data objects raw data or Python objects that need to be
231 Are the data objects raw data or Python objects that need to be
232 formatted before display? [default: False]
232 formatted before display? [default: False]
233 metadata : dict (optional)
233 metadata : dict (optional)
234 Metadata to be associated with the specific mimetype output.
234 Metadata to be associated with the specific mimetype output.
235 """
235 """
236 _display_mimetype('text/latex', objs, **kwargs)
236 _display_mimetype('text/latex', objs, **kwargs)
237
237
238
238
239 def display_json(*objs, **kwargs):
239 def display_json(*objs, **kwargs):
240 """Display the JSON representation of an object.
240 """Display the JSON representation of an object.
241
241
242 Note that not many frontends support displaying JSON.
242 Note that not many frontends support displaying JSON.
243
243
244 Parameters
244 Parameters
245 ----------
245 ----------
246 objs : tuple of objects
246 objs : tuple of objects
247 The Python objects to display, or if raw=True raw json data to
247 The Python objects to display, or if raw=True raw json data to
248 display.
248 display.
249 raw : bool
249 raw : bool
250 Are the data objects raw data or Python objects that need to be
250 Are the data objects raw data or Python objects that need to be
251 formatted before display? [default: False]
251 formatted before display? [default: False]
252 metadata : dict (optional)
252 metadata : dict (optional)
253 Metadata to be associated with the specific mimetype output.
253 Metadata to be associated with the specific mimetype output.
254 """
254 """
255 _display_mimetype('application/json', objs, **kwargs)
255 _display_mimetype('application/json', objs, **kwargs)
256
256
257
257
258 def display_javascript(*objs, **kwargs):
258 def display_javascript(*objs, **kwargs):
259 """Display the Javascript representation of an object.
259 """Display the Javascript representation of an object.
260
260
261 Parameters
261 Parameters
262 ----------
262 ----------
263 objs : tuple of objects
263 objs : tuple of objects
264 The Python objects to display, or if raw=True raw javascript data to
264 The Python objects to display, or if raw=True raw javascript data to
265 display.
265 display.
266 raw : bool
266 raw : bool
267 Are the data objects raw data or Python objects that need to be
267 Are the data objects raw data or Python objects that need to be
268 formatted before display? [default: False]
268 formatted before display? [default: False]
269 metadata : dict (optional)
269 metadata : dict (optional)
270 Metadata to be associated with the specific mimetype output.
270 Metadata to be associated with the specific mimetype output.
271 """
271 """
272 _display_mimetype('application/javascript', objs, **kwargs)
272 _display_mimetype('application/javascript', objs, **kwargs)
273
273
274
275 def display_pdf(*objs, **kwargs):
276 """Display the PDF representation of an object.
277
278 Parameters
279 ----------
280 objs : tuple of objects
281 The Python objects to display, or if raw=True raw javascript data to
282 display.
283 raw : bool
284 Are the data objects raw data or Python objects that need to be
285 formatted before display? [default: False]
286 metadata : dict (optional)
287 Metadata to be associated with the specific mimetype output.
288 """
289 _display_mimetype('application/pdf', objs, **kwargs)
290
291
274 #-----------------------------------------------------------------------------
292 #-----------------------------------------------------------------------------
275 # Smart classes
293 # Smart classes
276 #-----------------------------------------------------------------------------
294 #-----------------------------------------------------------------------------
277
295
278
296
279 class DisplayObject(object):
297 class DisplayObject(object):
280 """An object that wraps data to be displayed."""
298 """An object that wraps data to be displayed."""
281
299
282 _read_flags = 'r'
300 _read_flags = 'r'
283
301
284 def __init__(self, data=None, url=None, filename=None):
302 def __init__(self, data=None, url=None, filename=None):
285 """Create a display object given raw data.
303 """Create a display object given raw data.
286
304
287 When this object is returned by an expression or passed to the
305 When this object is returned by an expression or passed to the
288 display function, it will result in the data being displayed
306 display function, it will result in the data being displayed
289 in the frontend. The MIME type of the data should match the
307 in the frontend. The MIME type of the data should match the
290 subclasses used, so the Png subclass should be used for 'image/png'
308 subclasses used, so the Png subclass should be used for 'image/png'
291 data. If the data is a URL, the data will first be downloaded
309 data. If the data is a URL, the data will first be downloaded
292 and then displayed. If
310 and then displayed. If
293
311
294 Parameters
312 Parameters
295 ----------
313 ----------
296 data : unicode, str or bytes
314 data : unicode, str or bytes
297 The raw data or a URL or file to load the data from
315 The raw data or a URL or file to load the data from
298 url : unicode
316 url : unicode
299 A URL to download the data from.
317 A URL to download the data from.
300 filename : unicode
318 filename : unicode
301 Path to a local file to load the data from.
319 Path to a local file to load the data from.
302 """
320 """
303 if data is not None and isinstance(data, string_types):
321 if data is not None and isinstance(data, string_types):
304 if data.startswith('http') and url is None:
322 if data.startswith('http') and url is None:
305 url = data
323 url = data
306 filename = None
324 filename = None
307 data = None
325 data = None
308 elif _safe_exists(data) and filename is None:
326 elif _safe_exists(data) and filename is None:
309 url = None
327 url = None
310 filename = data
328 filename = data
311 data = None
329 data = None
312
330
313 self.data = data
331 self.data = data
314 self.url = url
332 self.url = url
315 self.filename = None if filename is None else unicode_type(filename)
333 self.filename = None if filename is None else unicode_type(filename)
316
334
317 self.reload()
335 self.reload()
318 self._check_data()
336 self._check_data()
319
337
320 def _check_data(self):
338 def _check_data(self):
321 """Override in subclasses if there's something to check."""
339 """Override in subclasses if there's something to check."""
322 pass
340 pass
323
341
324 def reload(self):
342 def reload(self):
325 """Reload the raw data from file or URL."""
343 """Reload the raw data from file or URL."""
326 if self.filename is not None:
344 if self.filename is not None:
327 with open(self.filename, self._read_flags) as f:
345 with open(self.filename, self._read_flags) as f:
328 self.data = f.read()
346 self.data = f.read()
329 elif self.url is not None:
347 elif self.url is not None:
330 try:
348 try:
331 try:
349 try:
332 from urllib.request import urlopen # Py3
350 from urllib.request import urlopen # Py3
333 except ImportError:
351 except ImportError:
334 from urllib2 import urlopen
352 from urllib2 import urlopen
335 response = urlopen(self.url)
353 response = urlopen(self.url)
336 self.data = response.read()
354 self.data = response.read()
337 # extract encoding from header, if there is one:
355 # extract encoding from header, if there is one:
338 encoding = None
356 encoding = None
339 for sub in response.headers['content-type'].split(';'):
357 for sub in response.headers['content-type'].split(';'):
340 sub = sub.strip()
358 sub = sub.strip()
341 if sub.startswith('charset'):
359 if sub.startswith('charset'):
342 encoding = sub.split('=')[-1].strip()
360 encoding = sub.split('=')[-1].strip()
343 break
361 break
344 # decode data, if an encoding was specified
362 # decode data, if an encoding was specified
345 if encoding:
363 if encoding:
346 self.data = self.data.decode(encoding, 'replace')
364 self.data = self.data.decode(encoding, 'replace')
347 except:
365 except:
348 self.data = None
366 self.data = None
349
367
350 class TextDisplayObject(DisplayObject):
368 class TextDisplayObject(DisplayObject):
351 """Validate that display data is text"""
369 """Validate that display data is text"""
352 def _check_data(self):
370 def _check_data(self):
353 if self.data is not None and not isinstance(self.data, string_types):
371 if self.data is not None and not isinstance(self.data, string_types):
354 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
372 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
355
373
356 class Pretty(TextDisplayObject):
374 class Pretty(TextDisplayObject):
357
375
358 def _repr_pretty_(self):
376 def _repr_pretty_(self):
359 return self.data
377 return self.data
360
378
361
379
362 class HTML(TextDisplayObject):
380 class HTML(TextDisplayObject):
363
381
364 def _repr_html_(self):
382 def _repr_html_(self):
365 return self.data
383 return self.data
366
384
367 def __html__(self):
385 def __html__(self):
368 """
386 """
369 This method exists to inform other HTML-using modules (e.g. Markupsafe,
387 This method exists to inform other HTML-using modules (e.g. Markupsafe,
370 htmltag, etc) that this object is HTML and does not need things like
388 htmltag, etc) that this object is HTML and does not need things like
371 special characters (<>&) escaped.
389 special characters (<>&) escaped.
372 """
390 """
373 return self._repr_html_()
391 return self._repr_html_()
374
392
375
393
376 class Math(TextDisplayObject):
394 class Math(TextDisplayObject):
377
395
378 def _repr_latex_(self):
396 def _repr_latex_(self):
379 s = self.data.strip('$')
397 s = self.data.strip('$')
380 return "$$%s$$" % s
398 return "$$%s$$" % s
381
399
382
400
383 class Latex(TextDisplayObject):
401 class Latex(TextDisplayObject):
384
402
385 def _repr_latex_(self):
403 def _repr_latex_(self):
386 return self.data
404 return self.data
387
405
388
406
389 class SVG(DisplayObject):
407 class SVG(DisplayObject):
390
408
391 # wrap data in a property, which extracts the <svg> tag, discarding
409 # wrap data in a property, which extracts the <svg> tag, discarding
392 # document headers
410 # document headers
393 _data = None
411 _data = None
394
412
395 @property
413 @property
396 def data(self):
414 def data(self):
397 return self._data
415 return self._data
398
416
399 @data.setter
417 @data.setter
400 def data(self, svg):
418 def data(self, svg):
401 if svg is None:
419 if svg is None:
402 self._data = None
420 self._data = None
403 return
421 return
404 # parse into dom object
422 # parse into dom object
405 from xml.dom import minidom
423 from xml.dom import minidom
406 svg = cast_bytes_py2(svg)
424 svg = cast_bytes_py2(svg)
407 x = minidom.parseString(svg)
425 x = minidom.parseString(svg)
408 # get svg tag (should be 1)
426 # get svg tag (should be 1)
409 found_svg = x.getElementsByTagName('svg')
427 found_svg = x.getElementsByTagName('svg')
410 if found_svg:
428 if found_svg:
411 svg = found_svg[0].toxml()
429 svg = found_svg[0].toxml()
412 else:
430 else:
413 # fallback on the input, trust the user
431 # fallback on the input, trust the user
414 # but this is probably an error.
432 # but this is probably an error.
415 pass
433 pass
416 svg = cast_unicode(svg)
434 svg = cast_unicode(svg)
417 self._data = svg
435 self._data = svg
418
436
419 def _repr_svg_(self):
437 def _repr_svg_(self):
420 return self.data
438 return self.data
421
439
422
440
423 class JSON(TextDisplayObject):
441 class JSON(TextDisplayObject):
424
442
425 def _repr_json_(self):
443 def _repr_json_(self):
426 return self.data
444 return self.data
427
445
428 css_t = """$("head").append($("<link/>").attr({
446 css_t = """$("head").append($("<link/>").attr({
429 rel: "stylesheet",
447 rel: "stylesheet",
430 type: "text/css",
448 type: "text/css",
431 href: "%s"
449 href: "%s"
432 }));
450 }));
433 """
451 """
434
452
435 lib_t1 = """$.getScript("%s", function () {
453 lib_t1 = """$.getScript("%s", function () {
436 """
454 """
437 lib_t2 = """});
455 lib_t2 = """});
438 """
456 """
439
457
440 class Javascript(TextDisplayObject):
458 class Javascript(TextDisplayObject):
441
459
442 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
460 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
443 """Create a Javascript display object given raw data.
461 """Create a Javascript display object given raw data.
444
462
445 When this object is returned by an expression or passed to the
463 When this object is returned by an expression or passed to the
446 display function, it will result in the data being displayed
464 display function, it will result in the data being displayed
447 in the frontend. If the data is a URL, the data will first be
465 in the frontend. If the data is a URL, the data will first be
448 downloaded and then displayed.
466 downloaded and then displayed.
449
467
450 In the Notebook, the containing element will be available as `element`,
468 In the Notebook, the containing element will be available as `element`,
451 and jQuery will be available. The output area starts hidden, so if
469 and jQuery will be available. The output area starts hidden, so if
452 the js appends content to `element` that should be visible, then
470 the js appends content to `element` that should be visible, then
453 it must call `container.show()` to unhide the area.
471 it must call `container.show()` to unhide the area.
454
472
455 Parameters
473 Parameters
456 ----------
474 ----------
457 data : unicode, str or bytes
475 data : unicode, str or bytes
458 The Javascript source code or a URL to download it from.
476 The Javascript source code or a URL to download it from.
459 url : unicode
477 url : unicode
460 A URL to download the data from.
478 A URL to download the data from.
461 filename : unicode
479 filename : unicode
462 Path to a local file to load the data from.
480 Path to a local file to load the data from.
463 lib : list or str
481 lib : list or str
464 A sequence of Javascript library URLs to load asynchronously before
482 A sequence of Javascript library URLs to load asynchronously before
465 running the source code. The full URLs of the libraries should
483 running the source code. The full URLs of the libraries should
466 be given. A single Javascript library URL can also be given as a
484 be given. A single Javascript library URL can also be given as a
467 string.
485 string.
468 css: : list or str
486 css: : list or str
469 A sequence of css files to load before running the source code.
487 A sequence of css files to load before running the source code.
470 The full URLs of the css files should be given. A single css URL
488 The full URLs of the css files should be given. A single css URL
471 can also be given as a string.
489 can also be given as a string.
472 """
490 """
473 if isinstance(lib, string_types):
491 if isinstance(lib, string_types):
474 lib = [lib]
492 lib = [lib]
475 elif lib is None:
493 elif lib is None:
476 lib = []
494 lib = []
477 if isinstance(css, string_types):
495 if isinstance(css, string_types):
478 css = [css]
496 css = [css]
479 elif css is None:
497 elif css is None:
480 css = []
498 css = []
481 if not isinstance(lib, (list,tuple)):
499 if not isinstance(lib, (list,tuple)):
482 raise TypeError('expected sequence, got: %r' % lib)
500 raise TypeError('expected sequence, got: %r' % lib)
483 if not isinstance(css, (list,tuple)):
501 if not isinstance(css, (list,tuple)):
484 raise TypeError('expected sequence, got: %r' % css)
502 raise TypeError('expected sequence, got: %r' % css)
485 self.lib = lib
503 self.lib = lib
486 self.css = css
504 self.css = css
487 super(Javascript, self).__init__(data=data, url=url, filename=filename)
505 super(Javascript, self).__init__(data=data, url=url, filename=filename)
488
506
489 def _repr_javascript_(self):
507 def _repr_javascript_(self):
490 r = ''
508 r = ''
491 for c in self.css:
509 for c in self.css:
492 r += css_t % c
510 r += css_t % c
493 for l in self.lib:
511 for l in self.lib:
494 r += lib_t1 % l
512 r += lib_t1 % l
495 r += self.data
513 r += self.data
496 r += lib_t2*len(self.lib)
514 r += lib_t2*len(self.lib)
497 return r
515 return r
498
516
499 # constants for identifying png/jpeg data
517 # constants for identifying png/jpeg data
500 _PNG = b'\x89PNG\r\n\x1a\n'
518 _PNG = b'\x89PNG\r\n\x1a\n'
501 _JPEG = b'\xff\xd8'
519 _JPEG = b'\xff\xd8'
502
520
503 def _pngxy(data):
521 def _pngxy(data):
504 """read the (width, height) from a PNG header"""
522 """read the (width, height) from a PNG header"""
505 ihdr = data.index(b'IHDR')
523 ihdr = data.index(b'IHDR')
506 # next 8 bytes are width/height
524 # next 8 bytes are width/height
507 w4h4 = data[ihdr+4:ihdr+12]
525 w4h4 = data[ihdr+4:ihdr+12]
508 return struct.unpack('>ii', w4h4)
526 return struct.unpack('>ii', w4h4)
509
527
510 def _jpegxy(data):
528 def _jpegxy(data):
511 """read the (width, height) from a JPEG header"""
529 """read the (width, height) from a JPEG header"""
512 # adapted from http://www.64lines.com/jpeg-width-height
530 # adapted from http://www.64lines.com/jpeg-width-height
513
531
514 idx = 4
532 idx = 4
515 while True:
533 while True:
516 block_size = struct.unpack('>H', data[idx:idx+2])[0]
534 block_size = struct.unpack('>H', data[idx:idx+2])[0]
517 idx = idx + block_size
535 idx = idx + block_size
518 if data[idx:idx+2] == b'\xFF\xC0':
536 if data[idx:idx+2] == b'\xFF\xC0':
519 # found Start of Frame
537 # found Start of Frame
520 iSOF = idx
538 iSOF = idx
521 break
539 break
522 else:
540 else:
523 # read another block
541 # read another block
524 idx += 2
542 idx += 2
525
543
526 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
544 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
527 return w, h
545 return w, h
528
546
529 class Image(DisplayObject):
547 class Image(DisplayObject):
530
548
531 _read_flags = 'rb'
549 _read_flags = 'rb'
532 _FMT_JPEG = u'jpeg'
550 _FMT_JPEG = u'jpeg'
533 _FMT_PNG = u'png'
551 _FMT_PNG = u'png'
534 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
552 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
535
553
536 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
554 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
537 """Create a PNG/JPEG image object given raw data.
555 """Create a PNG/JPEG image object given raw data.
538
556
539 When this object is returned by an input cell or passed to the
557 When this object is returned by an input cell or passed to the
540 display function, it will result in the image being displayed
558 display function, it will result in the image being displayed
541 in the frontend.
559 in the frontend.
542
560
543 Parameters
561 Parameters
544 ----------
562 ----------
545 data : unicode, str or bytes
563 data : unicode, str or bytes
546 The raw image data or a URL or filename to load the data from.
564 The raw image data or a URL or filename to load the data from.
547 This always results in embedded image data.
565 This always results in embedded image data.
548 url : unicode
566 url : unicode
549 A URL to download the data from. If you specify `url=`,
567 A URL to download the data from. If you specify `url=`,
550 the image data will not be embedded unless you also specify `embed=True`.
568 the image data will not be embedded unless you also specify `embed=True`.
551 filename : unicode
569 filename : unicode
552 Path to a local file to load the data from.
570 Path to a local file to load the data from.
553 Images from a file are always embedded.
571 Images from a file are always embedded.
554 format : unicode
572 format : unicode
555 The format of the image data (png/jpeg/jpg). If a filename or URL is given
573 The format of the image data (png/jpeg/jpg). If a filename or URL is given
556 for format will be inferred from the filename extension.
574 for format will be inferred from the filename extension.
557 embed : bool
575 embed : bool
558 Should the image data be embedded using a data URI (True) or be
576 Should the image data be embedded using a data URI (True) or be
559 loaded using an <img> tag. Set this to True if you want the image
577 loaded using an <img> tag. Set this to True if you want the image
560 to be viewable later with no internet connection in the notebook.
578 to be viewable later with no internet connection in the notebook.
561
579
562 Default is `True`, unless the keyword argument `url` is set, then
580 Default is `True`, unless the keyword argument `url` is set, then
563 default value is `False`.
581 default value is `False`.
564
582
565 Note that QtConsole is not able to display images if `embed` is set to `False`
583 Note that QtConsole is not able to display images if `embed` is set to `False`
566 width : int
584 width : int
567 Width to which to constrain the image in html
585 Width to which to constrain the image in html
568 height : int
586 height : int
569 Height to which to constrain the image in html
587 Height to which to constrain the image in html
570 retina : bool
588 retina : bool
571 Automatically set the width and height to half of the measured
589 Automatically set the width and height to half of the measured
572 width and height.
590 width and height.
573 This only works for embedded images because it reads the width/height
591 This only works for embedded images because it reads the width/height
574 from image data.
592 from image data.
575 For non-embedded images, you can just set the desired display width
593 For non-embedded images, you can just set the desired display width
576 and height directly.
594 and height directly.
577
595
578 Examples
596 Examples
579 --------
597 --------
580 # embedded image data, works in qtconsole and notebook
598 # embedded image data, works in qtconsole and notebook
581 # when passed positionally, the first arg can be any of raw image data,
599 # when passed positionally, the first arg can be any of raw image data,
582 # a URL, or a filename from which to load image data.
600 # a URL, or a filename from which to load image data.
583 # The result is always embedding image data for inline images.
601 # The result is always embedding image data for inline images.
584 Image('http://www.google.fr/images/srpr/logo3w.png')
602 Image('http://www.google.fr/images/srpr/logo3w.png')
585 Image('/path/to/image.jpg')
603 Image('/path/to/image.jpg')
586 Image(b'RAW_PNG_DATA...')
604 Image(b'RAW_PNG_DATA...')
587
605
588 # Specifying Image(url=...) does not embed the image data,
606 # Specifying Image(url=...) does not embed the image data,
589 # it only generates `<img>` tag with a link to the source.
607 # it only generates `<img>` tag with a link to the source.
590 # This will not work in the qtconsole or offline.
608 # This will not work in the qtconsole or offline.
591 Image(url='http://www.google.fr/images/srpr/logo3w.png')
609 Image(url='http://www.google.fr/images/srpr/logo3w.png')
592
610
593 """
611 """
594 if filename is not None:
612 if filename is not None:
595 ext = self._find_ext(filename)
613 ext = self._find_ext(filename)
596 elif url is not None:
614 elif url is not None:
597 ext = self._find_ext(url)
615 ext = self._find_ext(url)
598 elif data is None:
616 elif data is None:
599 raise ValueError("No image data found. Expecting filename, url, or data.")
617 raise ValueError("No image data found. Expecting filename, url, or data.")
600 elif isinstance(data, string_types) and (
618 elif isinstance(data, string_types) and (
601 data.startswith('http') or _safe_exists(data)
619 data.startswith('http') or _safe_exists(data)
602 ):
620 ):
603 ext = self._find_ext(data)
621 ext = self._find_ext(data)
604 else:
622 else:
605 ext = None
623 ext = None
606
624
607 if ext is not None:
625 if ext is not None:
608 format = ext.lower()
626 format = ext.lower()
609 if ext == u'jpg' or ext == u'jpeg':
627 if ext == u'jpg' or ext == u'jpeg':
610 format = self._FMT_JPEG
628 format = self._FMT_JPEG
611 if ext == u'png':
629 if ext == u'png':
612 format = self._FMT_PNG
630 format = self._FMT_PNG
613 elif isinstance(data, bytes) and format == 'png':
631 elif isinstance(data, bytes) and format == 'png':
614 # infer image type from image data header,
632 # infer image type from image data header,
615 # only if format might not have been specified.
633 # only if format might not have been specified.
616 if data[:2] == _JPEG:
634 if data[:2] == _JPEG:
617 format = 'jpeg'
635 format = 'jpeg'
618
636
619 self.format = unicode_type(format).lower()
637 self.format = unicode_type(format).lower()
620 self.embed = embed if embed is not None else (url is None)
638 self.embed = embed if embed is not None else (url is None)
621
639
622 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
640 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
623 raise ValueError("Cannot embed the '%s' image format" % (self.format))
641 raise ValueError("Cannot embed the '%s' image format" % (self.format))
624 self.width = width
642 self.width = width
625 self.height = height
643 self.height = height
626 self.retina = retina
644 self.retina = retina
627 super(Image, self).__init__(data=data, url=url, filename=filename)
645 super(Image, self).__init__(data=data, url=url, filename=filename)
628
646
629 if retina:
647 if retina:
630 self._retina_shape()
648 self._retina_shape()
631
649
632 def _retina_shape(self):
650 def _retina_shape(self):
633 """load pixel-doubled width and height from image data"""
651 """load pixel-doubled width and height from image data"""
634 if not self.embed:
652 if not self.embed:
635 return
653 return
636 if self.format == 'png':
654 if self.format == 'png':
637 w, h = _pngxy(self.data)
655 w, h = _pngxy(self.data)
638 elif self.format == 'jpeg':
656 elif self.format == 'jpeg':
639 w, h = _jpegxy(self.data)
657 w, h = _jpegxy(self.data)
640 else:
658 else:
641 # retina only supports png
659 # retina only supports png
642 return
660 return
643 self.width = w // 2
661 self.width = w // 2
644 self.height = h // 2
662 self.height = h // 2
645
663
646 def reload(self):
664 def reload(self):
647 """Reload the raw data from file or URL."""
665 """Reload the raw data from file or URL."""
648 if self.embed:
666 if self.embed:
649 super(Image,self).reload()
667 super(Image,self).reload()
650 if self.retina:
668 if self.retina:
651 self._retina_shape()
669 self._retina_shape()
652
670
653 def _repr_html_(self):
671 def _repr_html_(self):
654 if not self.embed:
672 if not self.embed:
655 width = height = ''
673 width = height = ''
656 if self.width:
674 if self.width:
657 width = ' width="%d"' % self.width
675 width = ' width="%d"' % self.width
658 if self.height:
676 if self.height:
659 height = ' height="%d"' % self.height
677 height = ' height="%d"' % self.height
660 return u'<img src="%s"%s%s/>' % (self.url, width, height)
678 return u'<img src="%s"%s%s/>' % (self.url, width, height)
661
679
662 def _data_and_metadata(self):
680 def _data_and_metadata(self):
663 """shortcut for returning metadata with shape information, if defined"""
681 """shortcut for returning metadata with shape information, if defined"""
664 md = {}
682 md = {}
665 if self.width:
683 if self.width:
666 md['width'] = self.width
684 md['width'] = self.width
667 if self.height:
685 if self.height:
668 md['height'] = self.height
686 md['height'] = self.height
669 if md:
687 if md:
670 return self.data, md
688 return self.data, md
671 else:
689 else:
672 return self.data
690 return self.data
673
691
674 def _repr_png_(self):
692 def _repr_png_(self):
675 if self.embed and self.format == u'png':
693 if self.embed and self.format == u'png':
676 return self._data_and_metadata()
694 return self._data_and_metadata()
677
695
678 def _repr_jpeg_(self):
696 def _repr_jpeg_(self):
679 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
697 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
680 return self._data_and_metadata()
698 return self._data_and_metadata()
681
699
682 def _find_ext(self, s):
700 def _find_ext(self, s):
683 return unicode_type(s.split('.')[-1].lower())
701 return unicode_type(s.split('.')[-1].lower())
684
702
685
703
686 def clear_output(wait=False):
704 def clear_output(wait=False):
687 """Clear the output of the current cell receiving output.
705 """Clear the output of the current cell receiving output.
688
706
689 Parameters
707 Parameters
690 ----------
708 ----------
691 wait : bool [default: false]
709 wait : bool [default: false]
692 Wait to clear the output until new output is available to replace it."""
710 Wait to clear the output until new output is available to replace it."""
693 from IPython.core.interactiveshell import InteractiveShell
711 from IPython.core.interactiveshell import InteractiveShell
694 if InteractiveShell.initialized():
712 if InteractiveShell.initialized():
695 InteractiveShell.instance().display_pub.clear_output(wait)
713 InteractiveShell.instance().display_pub.clear_output(wait)
696 else:
714 else:
697 from IPython.utils import io
715 from IPython.utils import io
698 print('\033[2K\r', file=io.stdout, end='')
716 print('\033[2K\r', file=io.stdout, end='')
699 io.stdout.flush()
717 io.stdout.flush()
700 print('\033[2K\r', file=io.stderr, end='')
718 print('\033[2K\r', file=io.stderr, end='')
701 io.stderr.flush()
719 io.stderr.flush()
720
721
722 @skip_doctest
723 def set_matplotlib_formats(*formats, **kwargs):
724 """Select figure formats for the inline backend. Optionally pass quality for JPEG.
725
726 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
727
728 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
729
730 To set this in your config files use the following::
731
732 c.InlineBackend.figure_formats = {'pdf', 'png', 'svg'}
733 c.InlineBackend.quality = 90
734
735 Parameters
736 ----------
737 *formats : list, tuple
738 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
739 quality : int
740 A percentage for the quality of JPEG figures. Defaults to 90.
741 """
742 from IPython.core.interactiveshell import InteractiveShell
743 from IPython.core.pylabtools import select_figure_formats
744 shell = InteractiveShell.instance()
745 select_figure_formats(shell, formats, quality=90)
746
747 @skip_doctest
748 def set_matplotlib_close(close):
749 """Set whether the inline backend closes all figures automatically or not.
750
751 By default, the inline backend used in the IPython Notebook will close all
752 matplotlib figures automatically after each cell is run. This means that
753 plots in different cells won't interfere. Sometimes, you may want to make
754 a plot in one cell and then refine it in later cells. This can be accomplished
755 by::
756
757 In [1]: set_matplotlib_close(False)
758
759 To set this in your config files use the following::
760
761 c.InlineBackend.close_figures = False
762
763 Parameters
764 ----------
765 close : bool
766 Should all matplotlib figures be automatically closed after each cell is
767 run?
768 """
769 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
770 ilbe = InlineBackend.instance()
771 ilbe.close_figures = close
772
@@ -1,825 +1,846 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Display formatters.
2 """Display formatters.
3
3
4 Inheritance diagram:
4 Inheritance diagram:
5
5
6 .. inheritance-diagram:: IPython.core.formatters
6 .. inheritance-diagram:: IPython.core.formatters
7 :parts: 3
7 :parts: 3
8
8
9 Authors:
9 Authors:
10
10
11 * Robert Kern
11 * Robert Kern
12 * Brian Granger
12 * Brian Granger
13 """
13 """
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Copyright (C) 2010-2011, IPython Development Team.
15 # Copyright (C) 2010-2011, IPython Development Team.
16 #
16 #
17 # Distributed under the terms of the Modified BSD License.
17 # Distributed under the terms of the Modified BSD License.
18 #
18 #
19 # The full license is in the file COPYING.txt, distributed with this software.
19 # The full license is in the file COPYING.txt, distributed with this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 # Stdlib imports
26 # Stdlib imports
27 import abc
27 import abc
28 import sys
28 import sys
29 import warnings
29 import warnings
30
30
31 from IPython.external.decorator import decorator
31 from IPython.external.decorator import decorator
32
32
33 # Our own imports
33 # Our own imports
34 from IPython.config.configurable import Configurable
34 from IPython.config.configurable import Configurable
35 from IPython.lib import pretty
35 from IPython.lib import pretty
36 from IPython.utils import io
36 from IPython.utils import io
37 from IPython.utils.traitlets import (
37 from IPython.utils.traitlets import (
38 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
38 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
39 )
39 )
40 from IPython.utils.warn import warn
40 from IPython.utils.warn import warn
41 from IPython.utils.py3compat import (
41 from IPython.utils.py3compat import (
42 unicode_to_str, with_metaclass, PY3, string_types, unicode_type,
42 unicode_to_str, with_metaclass, PY3, string_types, unicode_type,
43 )
43 )
44
44
45 if PY3:
45 if PY3:
46 from io import StringIO
46 from io import StringIO
47 else:
47 else:
48 from StringIO import StringIO
48 from StringIO import StringIO
49
49
50
50
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52 # The main DisplayFormatter class
52 # The main DisplayFormatter class
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54
54
55 class DisplayFormatter(Configurable):
55 class DisplayFormatter(Configurable):
56
56
57 # When set to true only the default plain text formatter will be used.
57 # When set to true only the default plain text formatter will be used.
58 plain_text_only = Bool(False, config=True)
58 plain_text_only = Bool(False, config=True)
59 def _plain_text_only_changed(self, name, old, new):
59 def _plain_text_only_changed(self, name, old, new):
60 warnings.warn("""DisplayFormatter.plain_text_only is deprecated.
60 warnings.warn("""DisplayFormatter.plain_text_only is deprecated.
61
61
62 Use DisplayFormatter.active_types = ['text/plain']
62 Use DisplayFormatter.active_types = ['text/plain']
63 for the same effect.
63 for the same effect.
64 """, DeprecationWarning)
64 """, DeprecationWarning)
65 if new:
65 if new:
66 self.active_types = ['text/plain']
66 self.active_types = ['text/plain']
67 else:
67 else:
68 self.active_types = self.format_types
68 self.active_types = self.format_types
69
69
70 active_types = List(Unicode, config=True,
70 active_types = List(Unicode, config=True,
71 help="""List of currently active mime-types to display.
71 help="""List of currently active mime-types to display.
72 You can use this to set a white-list for formats to display.
72 You can use this to set a white-list for formats to display.
73
73
74 Most users will not need to change this value.
74 Most users will not need to change this value.
75 """)
75 """)
76 def _active_types_default(self):
76 def _active_types_default(self):
77 return self.format_types
77 return self.format_types
78
78
79 def _active_types_changed(self, name, old, new):
79 def _active_types_changed(self, name, old, new):
80 for key, formatter in self.formatters.items():
80 for key, formatter in self.formatters.items():
81 if key in new:
81 if key in new:
82 formatter.enabled = True
82 formatter.enabled = True
83 else:
83 else:
84 formatter.enabled = False
84 formatter.enabled = False
85
85
86 # A dict of formatter whose keys are format types (MIME types) and whose
86 # A dict of formatter whose keys are format types (MIME types) and whose
87 # values are subclasses of BaseFormatter.
87 # values are subclasses of BaseFormatter.
88 formatters = Dict()
88 formatters = Dict()
89 def _formatters_default(self):
89 def _formatters_default(self):
90 """Activate the default formatters."""
90 """Activate the default formatters."""
91 formatter_classes = [
91 formatter_classes = [
92 PlainTextFormatter,
92 PlainTextFormatter,
93 HTMLFormatter,
93 HTMLFormatter,
94 SVGFormatter,
94 SVGFormatter,
95 PNGFormatter,
95 PNGFormatter,
96 PDFFormatter,
96 JPEGFormatter,
97 JPEGFormatter,
97 LatexFormatter,
98 LatexFormatter,
98 JSONFormatter,
99 JSONFormatter,
99 JavascriptFormatter
100 JavascriptFormatter
100 ]
101 ]
101 d = {}
102 d = {}
102 for cls in formatter_classes:
103 for cls in formatter_classes:
103 f = cls(parent=self)
104 f = cls(parent=self)
104 d[f.format_type] = f
105 d[f.format_type] = f
105 return d
106 return d
106
107
107 def format(self, obj, include=None, exclude=None):
108 def format(self, obj, include=None, exclude=None):
108 """Return a format data dict for an object.
109 """Return a format data dict for an object.
109
110
110 By default all format types will be computed.
111 By default all format types will be computed.
111
112
112 The following MIME types are currently implemented:
113 The following MIME types are currently implemented:
113
114
114 * text/plain
115 * text/plain
115 * text/html
116 * text/html
116 * text/latex
117 * text/latex
117 * application/json
118 * application/json
118 * application/javascript
119 * application/javascript
120 * application/pdf
119 * image/png
121 * image/png
120 * image/jpeg
122 * image/jpeg
121 * image/svg+xml
123 * image/svg+xml
122
124
123 Parameters
125 Parameters
124 ----------
126 ----------
125 obj : object
127 obj : object
126 The Python object whose format data will be computed.
128 The Python object whose format data will be computed.
127 include : list or tuple, optional
129 include : list or tuple, optional
128 A list of format type strings (MIME types) to include in the
130 A list of format type strings (MIME types) to include in the
129 format data dict. If this is set *only* the format types included
131 format data dict. If this is set *only* the format types included
130 in this list will be computed.
132 in this list will be computed.
131 exclude : list or tuple, optional
133 exclude : list or tuple, optional
132 A list of format type string (MIME types) to exclude in the format
134 A list of format type string (MIME types) to exclude in the format
133 data dict. If this is set all format types will be computed,
135 data dict. If this is set all format types will be computed,
134 except for those included in this argument.
136 except for those included in this argument.
135
137
136 Returns
138 Returns
137 -------
139 -------
138 (format_dict, metadata_dict) : tuple of two dicts
140 (format_dict, metadata_dict) : tuple of two dicts
139
141
140 format_dict is a dictionary of key/value pairs, one of each format that was
142 format_dict is a dictionary of key/value pairs, one of each format that was
141 generated for the object. The keys are the format types, which
143 generated for the object. The keys are the format types, which
142 will usually be MIME type strings and the values and JSON'able
144 will usually be MIME type strings and the values and JSON'able
143 data structure containing the raw data for the representation in
145 data structure containing the raw data for the representation in
144 that format.
146 that format.
145
147
146 metadata_dict is a dictionary of metadata about each mime-type output.
148 metadata_dict is a dictionary of metadata about each mime-type output.
147 Its keys will be a strict subset of the keys in format_dict.
149 Its keys will be a strict subset of the keys in format_dict.
148 """
150 """
149 format_dict = {}
151 format_dict = {}
150 md_dict = {}
152 md_dict = {}
151
153
152 for format_type, formatter in self.formatters.items():
154 for format_type, formatter in self.formatters.items():
153 if include and format_type not in include:
155 if include and format_type not in include:
154 continue
156 continue
155 if exclude and format_type in exclude:
157 if exclude and format_type in exclude:
156 continue
158 continue
157
159
158 md = None
160 md = None
159 try:
161 try:
160 data = formatter(obj)
162 data = formatter(obj)
161 except:
163 except:
162 # FIXME: log the exception
164 # FIXME: log the exception
163 raise
165 raise
164
166
165 # formatters can return raw data or (data, metadata)
167 # formatters can return raw data or (data, metadata)
166 if isinstance(data, tuple) and len(data) == 2:
168 if isinstance(data, tuple) and len(data) == 2:
167 data, md = data
169 data, md = data
168
170
169 if data is not None:
171 if data is not None:
170 format_dict[format_type] = data
172 format_dict[format_type] = data
171 if md is not None:
173 if md is not None:
172 md_dict[format_type] = md
174 md_dict[format_type] = md
173
175
174 return format_dict, md_dict
176 return format_dict, md_dict
175
177
176 @property
178 @property
177 def format_types(self):
179 def format_types(self):
178 """Return the format types (MIME types) of the active formatters."""
180 """Return the format types (MIME types) of the active formatters."""
179 return list(self.formatters.keys())
181 return list(self.formatters.keys())
180
182
181
183
182 #-----------------------------------------------------------------------------
184 #-----------------------------------------------------------------------------
183 # Formatters for specific format types (text, html, svg, etc.)
185 # Formatters for specific format types (text, html, svg, etc.)
184 #-----------------------------------------------------------------------------
186 #-----------------------------------------------------------------------------
185
187
186 class FormatterWarning(UserWarning):
188 class FormatterWarning(UserWarning):
187 """Warning class for errors in formatters"""
189 """Warning class for errors in formatters"""
188
190
189 @decorator
191 @decorator
190 def warn_format_error(method, self, *args, **kwargs):
192 def warn_format_error(method, self, *args, **kwargs):
191 """decorator for warning on failed format call"""
193 """decorator for warning on failed format call"""
192 try:
194 try:
193 r = method(self, *args, **kwargs)
195 r = method(self, *args, **kwargs)
194 except NotImplementedError as e:
196 except NotImplementedError as e:
195 # don't warn on NotImplementedErrors
197 # don't warn on NotImplementedErrors
196 return None
198 return None
197 except Exception as e:
199 except Exception as e:
198 warnings.warn("Exception in %s formatter: %s" % (self.format_type, e),
200 warnings.warn("Exception in %s formatter: %s" % (self.format_type, e),
199 FormatterWarning,
201 FormatterWarning,
200 )
202 )
201 return None
203 return None
202 if r is None or isinstance(r, self._return_type) or \
204 if r is None or isinstance(r, self._return_type) or \
203 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
205 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
204 return r
206 return r
205 else:
207 else:
206 warnings.warn(
208 warnings.warn(
207 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
209 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
208 (self.format_type, type(r), self._return_type, pretty._safe_repr(args[0])),
210 (self.format_type, type(r), self._return_type, pretty._safe_repr(args[0])),
209 FormatterWarning
211 FormatterWarning
210 )
212 )
211
213
212
214
213 class FormatterABC(with_metaclass(abc.ABCMeta, object)):
215 class FormatterABC(with_metaclass(abc.ABCMeta, object)):
214 """ Abstract base class for Formatters.
216 """ Abstract base class for Formatters.
215
217
216 A formatter is a callable class that is responsible for computing the
218 A formatter is a callable class that is responsible for computing the
217 raw format data for a particular format type (MIME type). For example,
219 raw format data for a particular format type (MIME type). For example,
218 an HTML formatter would have a format type of `text/html` and would return
220 an HTML formatter would have a format type of `text/html` and would return
219 the HTML representation of the object when called.
221 the HTML representation of the object when called.
220 """
222 """
221
223
222 # The format type of the data returned, usually a MIME type.
224 # The format type of the data returned, usually a MIME type.
223 format_type = 'text/plain'
225 format_type = 'text/plain'
224
226
225 # Is the formatter enabled...
227 # Is the formatter enabled...
226 enabled = True
228 enabled = True
227
229
228 @abc.abstractmethod
230 @abc.abstractmethod
229 @warn_format_error
231 @warn_format_error
230 def __call__(self, obj):
232 def __call__(self, obj):
231 """Return a JSON'able representation of the object.
233 """Return a JSON'able representation of the object.
232
234
233 If the object cannot be formatted by this formatter,
235 If the object cannot be formatted by this formatter,
234 warn and return None.
236 warn and return None.
235 """
237 """
236 return repr(obj)
238 return repr(obj)
237
239
238
240
239 def _mod_name_key(typ):
241 def _mod_name_key(typ):
240 """Return a (__module__, __name__) tuple for a type.
242 """Return a (__module__, __name__) tuple for a type.
241
243
242 Used as key in Formatter.deferred_printers.
244 Used as key in Formatter.deferred_printers.
243 """
245 """
244 module = getattr(typ, '__module__', None)
246 module = getattr(typ, '__module__', None)
245 name = getattr(typ, '__name__', None)
247 name = getattr(typ, '__name__', None)
246 return (module, name)
248 return (module, name)
247
249
248
250
249 def _get_type(obj):
251 def _get_type(obj):
250 """Return the type of an instance (old and new-style)"""
252 """Return the type of an instance (old and new-style)"""
251 return getattr(obj, '__class__', None) or type(obj)
253 return getattr(obj, '__class__', None) or type(obj)
252
254
253 _raise_key_error = object()
255 _raise_key_error = object()
254
256
255
257
256 class BaseFormatter(Configurable):
258 class BaseFormatter(Configurable):
257 """A base formatter class that is configurable.
259 """A base formatter class that is configurable.
258
260
259 This formatter should usually be used as the base class of all formatters.
261 This formatter should usually be used as the base class of all formatters.
260 It is a traited :class:`Configurable` class and includes an extensible
262 It is a traited :class:`Configurable` class and includes an extensible
261 API for users to determine how their objects are formatted. The following
263 API for users to determine how their objects are formatted. The following
262 logic is used to find a function to format an given object.
264 logic is used to find a function to format an given object.
263
265
264 1. The object is introspected to see if it has a method with the name
266 1. The object is introspected to see if it has a method with the name
265 :attr:`print_method`. If is does, that object is passed to that method
267 :attr:`print_method`. If is does, that object is passed to that method
266 for formatting.
268 for formatting.
267 2. If no print method is found, three internal dictionaries are consulted
269 2. If no print method is found, three internal dictionaries are consulted
268 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
270 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
269 and :attr:`deferred_printers`.
271 and :attr:`deferred_printers`.
270
272
271 Users should use these dictionaries to register functions that will be
273 Users should use these dictionaries to register functions that will be
272 used to compute the format data for their objects (if those objects don't
274 used to compute the format data for their objects (if those objects don't
273 have the special print methods). The easiest way of using these
275 have the special print methods). The easiest way of using these
274 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
276 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
275 methods.
277 methods.
276
278
277 If no function/callable is found to compute the format data, ``None`` is
279 If no function/callable is found to compute the format data, ``None`` is
278 returned and this format type is not used.
280 returned and this format type is not used.
279 """
281 """
280
282
281 format_type = Unicode('text/plain')
283 format_type = Unicode('text/plain')
282 _return_type = string_types
284 _return_type = string_types
283
285
284 enabled = Bool(True, config=True)
286 enabled = Bool(True, config=True)
285
287
286 print_method = ObjectName('__repr__')
288 print_method = ObjectName('__repr__')
287
289
288 # The singleton printers.
290 # The singleton printers.
289 # Maps the IDs of the builtin singleton objects to the format functions.
291 # Maps the IDs of the builtin singleton objects to the format functions.
290 singleton_printers = Dict(config=True)
292 singleton_printers = Dict(config=True)
291
293
292 # The type-specific printers.
294 # The type-specific printers.
293 # Map type objects to the format functions.
295 # Map type objects to the format functions.
294 type_printers = Dict(config=True)
296 type_printers = Dict(config=True)
295
297
296 # The deferred-import type-specific printers.
298 # The deferred-import type-specific printers.
297 # Map (modulename, classname) pairs to the format functions.
299 # Map (modulename, classname) pairs to the format functions.
298 deferred_printers = Dict(config=True)
300 deferred_printers = Dict(config=True)
299
301
300 @warn_format_error
302 @warn_format_error
301 def __call__(self, obj):
303 def __call__(self, obj):
302 """Compute the format for an object."""
304 """Compute the format for an object."""
303 if self.enabled:
305 if self.enabled:
304 # lookup registered printer
306 # lookup registered printer
305 try:
307 try:
306 printer = self.lookup(obj)
308 printer = self.lookup(obj)
307 except KeyError:
309 except KeyError:
308 pass
310 pass
309 else:
311 else:
310 return printer(obj)
312 return printer(obj)
311 # Finally look for special method names
313 # Finally look for special method names
312 method = pretty._safe_getattr(obj, self.print_method, None)
314 method = pretty._safe_getattr(obj, self.print_method, None)
313 if method is not None:
315 if method is not None:
314 return method()
316 return method()
315 return None
317 return None
316 else:
318 else:
317 return None
319 return None
318
320
319 def __contains__(self, typ):
321 def __contains__(self, typ):
320 """map in to lookup_by_type"""
322 """map in to lookup_by_type"""
321 try:
323 try:
322 self.lookup_by_type(typ)
324 self.lookup_by_type(typ)
323 except KeyError:
325 except KeyError:
324 return False
326 return False
325 else:
327 else:
326 return True
328 return True
327
329
328 def lookup(self, obj):
330 def lookup(self, obj):
329 """Look up the formatter for a given instance.
331 """Look up the formatter for a given instance.
330
332
331 Parameters
333 Parameters
332 ----------
334 ----------
333 obj : object instance
335 obj : object instance
334
336
335 Returns
337 Returns
336 -------
338 -------
337 f : callable
339 f : callable
338 The registered formatting callable for the type.
340 The registered formatting callable for the type.
339
341
340 Raises
342 Raises
341 ------
343 ------
342 KeyError if the type has not been registered.
344 KeyError if the type has not been registered.
343 """
345 """
344 # look for singleton first
346 # look for singleton first
345 obj_id = id(obj)
347 obj_id = id(obj)
346 if obj_id in self.singleton_printers:
348 if obj_id in self.singleton_printers:
347 return self.singleton_printers[obj_id]
349 return self.singleton_printers[obj_id]
348 # then lookup by type
350 # then lookup by type
349 return self.lookup_by_type(_get_type(obj))
351 return self.lookup_by_type(_get_type(obj))
350
352
351 def lookup_by_type(self, typ):
353 def lookup_by_type(self, typ):
352 """Look up the registered formatter for a type.
354 """Look up the registered formatter for a type.
353
355
354 Parameters
356 Parameters
355 ----------
357 ----------
356 typ : type or '__module__.__name__' string for a type
358 typ : type or '__module__.__name__' string for a type
357
359
358 Returns
360 Returns
359 -------
361 -------
360 f : callable
362 f : callable
361 The registered formatting callable for the type.
363 The registered formatting callable for the type.
362
364
363 Raises
365 Raises
364 ------
366 ------
365 KeyError if the type has not been registered.
367 KeyError if the type has not been registered.
366 """
368 """
367 if isinstance(typ, string_types):
369 if isinstance(typ, string_types):
368 typ_key = tuple(typ.rsplit('.',1))
370 typ_key = tuple(typ.rsplit('.',1))
369 if typ_key not in self.deferred_printers:
371 if typ_key not in self.deferred_printers:
370 # We may have it cached in the type map. We will have to
372 # We may have it cached in the type map. We will have to
371 # iterate over all of the types to check.
373 # iterate over all of the types to check.
372 for cls in self.type_printers:
374 for cls in self.type_printers:
373 if _mod_name_key(cls) == typ_key:
375 if _mod_name_key(cls) == typ_key:
374 return self.type_printers[cls]
376 return self.type_printers[cls]
375 else:
377 else:
376 return self.deferred_printers[typ_key]
378 return self.deferred_printers[typ_key]
377 else:
379 else:
378 for cls in pretty._get_mro(typ):
380 for cls in pretty._get_mro(typ):
379 if cls in self.type_printers or self._in_deferred_types(cls):
381 if cls in self.type_printers or self._in_deferred_types(cls):
380 return self.type_printers[cls]
382 return self.type_printers[cls]
381
383
382 # If we have reached here, the lookup failed.
384 # If we have reached here, the lookup failed.
383 raise KeyError("No registered printer for {0!r}".format(typ))
385 raise KeyError("No registered printer for {0!r}".format(typ))
384
386
385 def for_type(self, typ, func=None):
387 def for_type(self, typ, func=None):
386 """Add a format function for a given type.
388 """Add a format function for a given type.
387
389
388 Parameters
390 Parameters
389 -----------
391 -----------
390 typ : type or '__module__.__name__' string for a type
392 typ : type or '__module__.__name__' string for a type
391 The class of the object that will be formatted using `func`.
393 The class of the object that will be formatted using `func`.
392 func : callable
394 func : callable
393 A callable for computing the format data.
395 A callable for computing the format data.
394 `func` will be called with the object to be formatted,
396 `func` will be called with the object to be formatted,
395 and will return the raw data in this formatter's format.
397 and will return the raw data in this formatter's format.
396 Subclasses may use a different call signature for the
398 Subclasses may use a different call signature for the
397 `func` argument.
399 `func` argument.
398
400
399 If `func` is None or not specified, there will be no change,
401 If `func` is None or not specified, there will be no change,
400 only returning the current value.
402 only returning the current value.
401
403
402 Returns
404 Returns
403 -------
405 -------
404 oldfunc : callable
406 oldfunc : callable
405 The currently registered callable.
407 The currently registered callable.
406 If you are registering a new formatter,
408 If you are registering a new formatter,
407 this will be the previous value (to enable restoring later).
409 this will be the previous value (to enable restoring later).
408 """
410 """
409 # if string given, interpret as 'pkg.module.class_name'
411 # if string given, interpret as 'pkg.module.class_name'
410 if isinstance(typ, string_types):
412 if isinstance(typ, string_types):
411 type_module, type_name = typ.rsplit('.', 1)
413 type_module, type_name = typ.rsplit('.', 1)
412 return self.for_type_by_name(type_module, type_name, func)
414 return self.for_type_by_name(type_module, type_name, func)
413
415
414 try:
416 try:
415 oldfunc = self.lookup_by_type(typ)
417 oldfunc = self.lookup_by_type(typ)
416 except KeyError:
418 except KeyError:
417 oldfunc = None
419 oldfunc = None
418
420
419 if func is not None:
421 if func is not None:
420 self.type_printers[typ] = func
422 self.type_printers[typ] = func
421
423
422 return oldfunc
424 return oldfunc
423
425
424 def for_type_by_name(self, type_module, type_name, func=None):
426 def for_type_by_name(self, type_module, type_name, func=None):
425 """Add a format function for a type specified by the full dotted
427 """Add a format function for a type specified by the full dotted
426 module and name of the type, rather than the type of the object.
428 module and name of the type, rather than the type of the object.
427
429
428 Parameters
430 Parameters
429 ----------
431 ----------
430 type_module : str
432 type_module : str
431 The full dotted name of the module the type is defined in, like
433 The full dotted name of the module the type is defined in, like
432 ``numpy``.
434 ``numpy``.
433 type_name : str
435 type_name : str
434 The name of the type (the class name), like ``dtype``
436 The name of the type (the class name), like ``dtype``
435 func : callable
437 func : callable
436 A callable for computing the format data.
438 A callable for computing the format data.
437 `func` will be called with the object to be formatted,
439 `func` will be called with the object to be formatted,
438 and will return the raw data in this formatter's format.
440 and will return the raw data in this formatter's format.
439 Subclasses may use a different call signature for the
441 Subclasses may use a different call signature for the
440 `func` argument.
442 `func` argument.
441
443
442 If `func` is None or unspecified, there will be no change,
444 If `func` is None or unspecified, there will be no change,
443 only returning the current value.
445 only returning the current value.
444
446
445 Returns
447 Returns
446 -------
448 -------
447 oldfunc : callable
449 oldfunc : callable
448 The currently registered callable.
450 The currently registered callable.
449 If you are registering a new formatter,
451 If you are registering a new formatter,
450 this will be the previous value (to enable restoring later).
452 this will be the previous value (to enable restoring later).
451 """
453 """
452 key = (type_module, type_name)
454 key = (type_module, type_name)
453
455
454 try:
456 try:
455 oldfunc = self.lookup_by_type("%s.%s" % key)
457 oldfunc = self.lookup_by_type("%s.%s" % key)
456 except KeyError:
458 except KeyError:
457 oldfunc = None
459 oldfunc = None
458
460
459 if func is not None:
461 if func is not None:
460 self.deferred_printers[key] = func
462 self.deferred_printers[key] = func
461 return oldfunc
463 return oldfunc
462
464
463 def pop(self, typ, default=_raise_key_error):
465 def pop(self, typ, default=_raise_key_error):
464 """Pop a formatter for the given type.
466 """Pop a formatter for the given type.
465
467
466 Parameters
468 Parameters
467 ----------
469 ----------
468 typ : type or '__module__.__name__' string for a type
470 typ : type or '__module__.__name__' string for a type
469 default : object
471 default : object
470 value to be returned if no formatter is registered for typ.
472 value to be returned if no formatter is registered for typ.
471
473
472 Returns
474 Returns
473 -------
475 -------
474 obj : object
476 obj : object
475 The last registered object for the type.
477 The last registered object for the type.
476
478
477 Raises
479 Raises
478 ------
480 ------
479 KeyError if the type is not registered and default is not specified.
481 KeyError if the type is not registered and default is not specified.
480 """
482 """
481
483
482 if isinstance(typ, string_types):
484 if isinstance(typ, string_types):
483 typ_key = tuple(typ.rsplit('.',1))
485 typ_key = tuple(typ.rsplit('.',1))
484 if typ_key not in self.deferred_printers:
486 if typ_key not in self.deferred_printers:
485 # We may have it cached in the type map. We will have to
487 # We may have it cached in the type map. We will have to
486 # iterate over all of the types to check.
488 # iterate over all of the types to check.
487 for cls in self.type_printers:
489 for cls in self.type_printers:
488 if _mod_name_key(cls) == typ_key:
490 if _mod_name_key(cls) == typ_key:
489 old = self.type_printers.pop(cls)
491 old = self.type_printers.pop(cls)
490 break
492 break
491 else:
493 else:
492 old = default
494 old = default
493 else:
495 else:
494 old = self.deferred_printers.pop(typ_key)
496 old = self.deferred_printers.pop(typ_key)
495 else:
497 else:
496 if typ in self.type_printers:
498 if typ in self.type_printers:
497 old = self.type_printers.pop(typ)
499 old = self.type_printers.pop(typ)
498 else:
500 else:
499 old = self.deferred_printers.pop(_mod_name_key(typ), default)
501 old = self.deferred_printers.pop(_mod_name_key(typ), default)
500 if old is _raise_key_error:
502 if old is _raise_key_error:
501 raise KeyError("No registered value for {0!r}".format(typ))
503 raise KeyError("No registered value for {0!r}".format(typ))
502 return old
504 return old
503
505
504 def _in_deferred_types(self, cls):
506 def _in_deferred_types(self, cls):
505 """
507 """
506 Check if the given class is specified in the deferred type registry.
508 Check if the given class is specified in the deferred type registry.
507
509
508 Successful matches will be moved to the regular type registry for future use.
510 Successful matches will be moved to the regular type registry for future use.
509 """
511 """
510 mod = getattr(cls, '__module__', None)
512 mod = getattr(cls, '__module__', None)
511 name = getattr(cls, '__name__', None)
513 name = getattr(cls, '__name__', None)
512 key = (mod, name)
514 key = (mod, name)
513 if key in self.deferred_printers:
515 if key in self.deferred_printers:
514 # Move the printer over to the regular registry.
516 # Move the printer over to the regular registry.
515 printer = self.deferred_printers.pop(key)
517 printer = self.deferred_printers.pop(key)
516 self.type_printers[cls] = printer
518 self.type_printers[cls] = printer
517 return True
519 return True
518 return False
520 return False
519
521
520
522
521 class PlainTextFormatter(BaseFormatter):
523 class PlainTextFormatter(BaseFormatter):
522 """The default pretty-printer.
524 """The default pretty-printer.
523
525
524 This uses :mod:`IPython.lib.pretty` to compute the format data of
526 This uses :mod:`IPython.lib.pretty` to compute the format data of
525 the object. If the object cannot be pretty printed, :func:`repr` is used.
527 the object. If the object cannot be pretty printed, :func:`repr` is used.
526 See the documentation of :mod:`IPython.lib.pretty` for details on
528 See the documentation of :mod:`IPython.lib.pretty` for details on
527 how to write pretty printers. Here is a simple example::
529 how to write pretty printers. Here is a simple example::
528
530
529 def dtype_pprinter(obj, p, cycle):
531 def dtype_pprinter(obj, p, cycle):
530 if cycle:
532 if cycle:
531 return p.text('dtype(...)')
533 return p.text('dtype(...)')
532 if hasattr(obj, 'fields'):
534 if hasattr(obj, 'fields'):
533 if obj.fields is None:
535 if obj.fields is None:
534 p.text(repr(obj))
536 p.text(repr(obj))
535 else:
537 else:
536 p.begin_group(7, 'dtype([')
538 p.begin_group(7, 'dtype([')
537 for i, field in enumerate(obj.descr):
539 for i, field in enumerate(obj.descr):
538 if i > 0:
540 if i > 0:
539 p.text(',')
541 p.text(',')
540 p.breakable()
542 p.breakable()
541 p.pretty(field)
543 p.pretty(field)
542 p.end_group(7, '])')
544 p.end_group(7, '])')
543 """
545 """
544
546
545 # The format type of data returned.
547 # The format type of data returned.
546 format_type = Unicode('text/plain')
548 format_type = Unicode('text/plain')
547
549
548 # This subclass ignores this attribute as it always need to return
550 # This subclass ignores this attribute as it always need to return
549 # something.
551 # something.
550 enabled = Bool(True, config=False)
552 enabled = Bool(True, config=False)
551
553
552 # Look for a _repr_pretty_ methods to use for pretty printing.
554 # Look for a _repr_pretty_ methods to use for pretty printing.
553 print_method = ObjectName('_repr_pretty_')
555 print_method = ObjectName('_repr_pretty_')
554
556
555 # Whether to pretty-print or not.
557 # Whether to pretty-print or not.
556 pprint = Bool(True, config=True)
558 pprint = Bool(True, config=True)
557
559
558 # Whether to be verbose or not.
560 # Whether to be verbose or not.
559 verbose = Bool(False, config=True)
561 verbose = Bool(False, config=True)
560
562
561 # The maximum width.
563 # The maximum width.
562 max_width = Integer(79, config=True)
564 max_width = Integer(79, config=True)
563
565
564 # The newline character.
566 # The newline character.
565 newline = Unicode('\n', config=True)
567 newline = Unicode('\n', config=True)
566
568
567 # format-string for pprinting floats
569 # format-string for pprinting floats
568 float_format = Unicode('%r')
570 float_format = Unicode('%r')
569 # setter for float precision, either int or direct format-string
571 # setter for float precision, either int or direct format-string
570 float_precision = CUnicode('', config=True)
572 float_precision = CUnicode('', config=True)
571
573
572 def _float_precision_changed(self, name, old, new):
574 def _float_precision_changed(self, name, old, new):
573 """float_precision changed, set float_format accordingly.
575 """float_precision changed, set float_format accordingly.
574
576
575 float_precision can be set by int or str.
577 float_precision can be set by int or str.
576 This will set float_format, after interpreting input.
578 This will set float_format, after interpreting input.
577 If numpy has been imported, numpy print precision will also be set.
579 If numpy has been imported, numpy print precision will also be set.
578
580
579 integer `n` sets format to '%.nf', otherwise, format set directly.
581 integer `n` sets format to '%.nf', otherwise, format set directly.
580
582
581 An empty string returns to defaults (repr for float, 8 for numpy).
583 An empty string returns to defaults (repr for float, 8 for numpy).
582
584
583 This parameter can be set via the '%precision' magic.
585 This parameter can be set via the '%precision' magic.
584 """
586 """
585
587
586 if '%' in new:
588 if '%' in new:
587 # got explicit format string
589 # got explicit format string
588 fmt = new
590 fmt = new
589 try:
591 try:
590 fmt%3.14159
592 fmt%3.14159
591 except Exception:
593 except Exception:
592 raise ValueError("Precision must be int or format string, not %r"%new)
594 raise ValueError("Precision must be int or format string, not %r"%new)
593 elif new:
595 elif new:
594 # otherwise, should be an int
596 # otherwise, should be an int
595 try:
597 try:
596 i = int(new)
598 i = int(new)
597 assert i >= 0
599 assert i >= 0
598 except ValueError:
600 except ValueError:
599 raise ValueError("Precision must be int or format string, not %r"%new)
601 raise ValueError("Precision must be int or format string, not %r"%new)
600 except AssertionError:
602 except AssertionError:
601 raise ValueError("int precision must be non-negative, not %r"%i)
603 raise ValueError("int precision must be non-negative, not %r"%i)
602
604
603 fmt = '%%.%if'%i
605 fmt = '%%.%if'%i
604 if 'numpy' in sys.modules:
606 if 'numpy' in sys.modules:
605 # set numpy precision if it has been imported
607 # set numpy precision if it has been imported
606 import numpy
608 import numpy
607 numpy.set_printoptions(precision=i)
609 numpy.set_printoptions(precision=i)
608 else:
610 else:
609 # default back to repr
611 # default back to repr
610 fmt = '%r'
612 fmt = '%r'
611 if 'numpy' in sys.modules:
613 if 'numpy' in sys.modules:
612 import numpy
614 import numpy
613 # numpy default is 8
615 # numpy default is 8
614 numpy.set_printoptions(precision=8)
616 numpy.set_printoptions(precision=8)
615 self.float_format = fmt
617 self.float_format = fmt
616
618
617 # Use the default pretty printers from IPython.lib.pretty.
619 # Use the default pretty printers from IPython.lib.pretty.
618 def _singleton_printers_default(self):
620 def _singleton_printers_default(self):
619 return pretty._singleton_pprinters.copy()
621 return pretty._singleton_pprinters.copy()
620
622
621 def _type_printers_default(self):
623 def _type_printers_default(self):
622 d = pretty._type_pprinters.copy()
624 d = pretty._type_pprinters.copy()
623 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
625 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
624 return d
626 return d
625
627
626 def _deferred_printers_default(self):
628 def _deferred_printers_default(self):
627 return pretty._deferred_type_pprinters.copy()
629 return pretty._deferred_type_pprinters.copy()
628
630
629 #### FormatterABC interface ####
631 #### FormatterABC interface ####
630
632
631 @warn_format_error
633 @warn_format_error
632 def __call__(self, obj):
634 def __call__(self, obj):
633 """Compute the pretty representation of the object."""
635 """Compute the pretty representation of the object."""
634 if not self.pprint:
636 if not self.pprint:
635 return pretty._safe_repr(obj)
637 return pretty._safe_repr(obj)
636 else:
638 else:
637 # This uses use StringIO, as cStringIO doesn't handle unicode.
639 # This uses use StringIO, as cStringIO doesn't handle unicode.
638 stream = StringIO()
640 stream = StringIO()
639 # self.newline.encode() is a quick fix for issue gh-597. We need to
641 # self.newline.encode() is a quick fix for issue gh-597. We need to
640 # ensure that stream does not get a mix of unicode and bytestrings,
642 # ensure that stream does not get a mix of unicode and bytestrings,
641 # or it will cause trouble.
643 # or it will cause trouble.
642 printer = pretty.RepresentationPrinter(stream, self.verbose,
644 printer = pretty.RepresentationPrinter(stream, self.verbose,
643 self.max_width, unicode_to_str(self.newline),
645 self.max_width, unicode_to_str(self.newline),
644 singleton_pprinters=self.singleton_printers,
646 singleton_pprinters=self.singleton_printers,
645 type_pprinters=self.type_printers,
647 type_pprinters=self.type_printers,
646 deferred_pprinters=self.deferred_printers)
648 deferred_pprinters=self.deferred_printers)
647 printer.pretty(obj)
649 printer.pretty(obj)
648 printer.flush()
650 printer.flush()
649 return stream.getvalue()
651 return stream.getvalue()
650
652
651
653
652 class HTMLFormatter(BaseFormatter):
654 class HTMLFormatter(BaseFormatter):
653 """An HTML formatter.
655 """An HTML formatter.
654
656
655 To define the callables that compute the HTML representation of your
657 To define the callables that compute the HTML representation of your
656 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
658 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
657 or :meth:`for_type_by_name` methods to register functions that handle
659 or :meth:`for_type_by_name` methods to register functions that handle
658 this.
660 this.
659
661
660 The return value of this formatter should be a valid HTML snippet that
662 The return value of this formatter should be a valid HTML snippet that
661 could be injected into an existing DOM. It should *not* include the
663 could be injected into an existing DOM. It should *not* include the
662 ```<html>`` or ```<body>`` tags.
664 ```<html>`` or ```<body>`` tags.
663 """
665 """
664 format_type = Unicode('text/html')
666 format_type = Unicode('text/html')
665
667
666 print_method = ObjectName('_repr_html_')
668 print_method = ObjectName('_repr_html_')
667
669
668
670
669 class SVGFormatter(BaseFormatter):
671 class SVGFormatter(BaseFormatter):
670 """An SVG formatter.
672 """An SVG formatter.
671
673
672 To define the callables that compute the SVG representation of your
674 To define the callables that compute the SVG representation of your
673 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
675 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
674 or :meth:`for_type_by_name` methods to register functions that handle
676 or :meth:`for_type_by_name` methods to register functions that handle
675 this.
677 this.
676
678
677 The return value of this formatter should be valid SVG enclosed in
679 The return value of this formatter should be valid SVG enclosed in
678 ```<svg>``` tags, that could be injected into an existing DOM. It should
680 ```<svg>``` tags, that could be injected into an existing DOM. It should
679 *not* include the ```<html>`` or ```<body>`` tags.
681 *not* include the ```<html>`` or ```<body>`` tags.
680 """
682 """
681 format_type = Unicode('image/svg+xml')
683 format_type = Unicode('image/svg+xml')
682
684
683 print_method = ObjectName('_repr_svg_')
685 print_method = ObjectName('_repr_svg_')
684
686
685
687
686 class PNGFormatter(BaseFormatter):
688 class PNGFormatter(BaseFormatter):
687 """A PNG formatter.
689 """A PNG formatter.
688
690
689 To define the callables that compute the PNG representation of your
691 To define the callables that compute the PNG representation of your
690 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
692 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
691 or :meth:`for_type_by_name` methods to register functions that handle
693 or :meth:`for_type_by_name` methods to register functions that handle
692 this.
694 this.
693
695
694 The return value of this formatter should be raw PNG data, *not*
696 The return value of this formatter should be raw PNG data, *not*
695 base64 encoded.
697 base64 encoded.
696 """
698 """
697 format_type = Unicode('image/png')
699 format_type = Unicode('image/png')
698
700
699 print_method = ObjectName('_repr_png_')
701 print_method = ObjectName('_repr_png_')
700
702
701 _return_type = (bytes, unicode_type)
703 _return_type = (bytes, unicode_type)
702
704
703
705
704 class JPEGFormatter(BaseFormatter):
706 class JPEGFormatter(BaseFormatter):
705 """A JPEG formatter.
707 """A JPEG formatter.
706
708
707 To define the callables that compute the JPEG representation of your
709 To define the callables that compute the JPEG representation of your
708 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
710 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
709 or :meth:`for_type_by_name` methods to register functions that handle
711 or :meth:`for_type_by_name` methods to register functions that handle
710 this.
712 this.
711
713
712 The return value of this formatter should be raw JPEG data, *not*
714 The return value of this formatter should be raw JPEG data, *not*
713 base64 encoded.
715 base64 encoded.
714 """
716 """
715 format_type = Unicode('image/jpeg')
717 format_type = Unicode('image/jpeg')
716
718
717 print_method = ObjectName('_repr_jpeg_')
719 print_method = ObjectName('_repr_jpeg_')
718
720
719 _return_type = (bytes, unicode_type)
721 _return_type = (bytes, unicode_type)
720
722
721
723
722 class LatexFormatter(BaseFormatter):
724 class LatexFormatter(BaseFormatter):
723 """A LaTeX formatter.
725 """A LaTeX formatter.
724
726
725 To define the callables that compute the LaTeX representation of your
727 To define the callables that compute the LaTeX representation of your
726 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
728 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
727 or :meth:`for_type_by_name` methods to register functions that handle
729 or :meth:`for_type_by_name` methods to register functions that handle
728 this.
730 this.
729
731
730 The return value of this formatter should be a valid LaTeX equation,
732 The return value of this formatter should be a valid LaTeX equation,
731 enclosed in either ```$```, ```$$``` or another LaTeX equation
733 enclosed in either ```$```, ```$$``` or another LaTeX equation
732 environment.
734 environment.
733 """
735 """
734 format_type = Unicode('text/latex')
736 format_type = Unicode('text/latex')
735
737
736 print_method = ObjectName('_repr_latex_')
738 print_method = ObjectName('_repr_latex_')
737
739
738
740
739 class JSONFormatter(BaseFormatter):
741 class JSONFormatter(BaseFormatter):
740 """A JSON string formatter.
742 """A JSON string formatter.
741
743
742 To define the callables that compute the JSON string representation of
744 To define the callables that compute the JSON string representation of
743 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
745 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
744 or :meth:`for_type_by_name` methods to register functions that handle
746 or :meth:`for_type_by_name` methods to register functions that handle
745 this.
747 this.
746
748
747 The return value of this formatter should be a valid JSON string.
749 The return value of this formatter should be a valid JSON string.
748 """
750 """
749 format_type = Unicode('application/json')
751 format_type = Unicode('application/json')
750
752
751 print_method = ObjectName('_repr_json_')
753 print_method = ObjectName('_repr_json_')
752
754
753
755
754 class JavascriptFormatter(BaseFormatter):
756 class JavascriptFormatter(BaseFormatter):
755 """A Javascript formatter.
757 """A Javascript formatter.
756
758
757 To define the callables that compute the Javascript representation of
759 To define the callables that compute the Javascript representation of
758 your objects, define a :meth:`_repr_javascript_` method or use the
760 your objects, define a :meth:`_repr_javascript_` method or use the
759 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
761 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
760 that handle this.
762 that handle this.
761
763
762 The return value of this formatter should be valid Javascript code and
764 The return value of this formatter should be valid Javascript code and
763 should *not* be enclosed in ```<script>``` tags.
765 should *not* be enclosed in ```<script>``` tags.
764 """
766 """
765 format_type = Unicode('application/javascript')
767 format_type = Unicode('application/javascript')
766
768
767 print_method = ObjectName('_repr_javascript_')
769 print_method = ObjectName('_repr_javascript_')
768
770
771
772 class PDFFormatter(BaseFormatter):
773 """A PDF formatter.
774
775 To defined the callables that compute to PDF representation of your
776 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
777 or :meth:`for_type_by_name` methods to register functions that handle
778 this.
779
780 The return value of this formatter should be raw PDF data, *not*
781 base64 encoded.
782 """
783 format_type = Unicode('application/pdf')
784
785 print_method = ObjectName('_repr_pdf_')
786
787
769 FormatterABC.register(BaseFormatter)
788 FormatterABC.register(BaseFormatter)
770 FormatterABC.register(PlainTextFormatter)
789 FormatterABC.register(PlainTextFormatter)
771 FormatterABC.register(HTMLFormatter)
790 FormatterABC.register(HTMLFormatter)
772 FormatterABC.register(SVGFormatter)
791 FormatterABC.register(SVGFormatter)
773 FormatterABC.register(PNGFormatter)
792 FormatterABC.register(PNGFormatter)
793 FormatterABC.register(PDFFormatter)
774 FormatterABC.register(JPEGFormatter)
794 FormatterABC.register(JPEGFormatter)
775 FormatterABC.register(LatexFormatter)
795 FormatterABC.register(LatexFormatter)
776 FormatterABC.register(JSONFormatter)
796 FormatterABC.register(JSONFormatter)
777 FormatterABC.register(JavascriptFormatter)
797 FormatterABC.register(JavascriptFormatter)
778
798
779
799
780 def format_display_data(obj, include=None, exclude=None):
800 def format_display_data(obj, include=None, exclude=None):
781 """Return a format data dict for an object.
801 """Return a format data dict for an object.
782
802
783 By default all format types will be computed.
803 By default all format types will be computed.
784
804
785 The following MIME types are currently implemented:
805 The following MIME types are currently implemented:
786
806
787 * text/plain
807 * text/plain
788 * text/html
808 * text/html
789 * text/latex
809 * text/latex
790 * application/json
810 * application/json
791 * application/javascript
811 * application/javascript
812 * application/pdf
792 * image/png
813 * image/png
793 * image/jpeg
814 * image/jpeg
794 * image/svg+xml
815 * image/svg+xml
795
816
796 Parameters
817 Parameters
797 ----------
818 ----------
798 obj : object
819 obj : object
799 The Python object whose format data will be computed.
820 The Python object whose format data will be computed.
800
821
801 Returns
822 Returns
802 -------
823 -------
803 format_dict : dict
824 format_dict : dict
804 A dictionary of key/value pairs, one or each format that was
825 A dictionary of key/value pairs, one or each format that was
805 generated for the object. The keys are the format types, which
826 generated for the object. The keys are the format types, which
806 will usually be MIME type strings and the values and JSON'able
827 will usually be MIME type strings and the values and JSON'able
807 data structure containing the raw data for the representation in
828 data structure containing the raw data for the representation in
808 that format.
829 that format.
809 include : list or tuple, optional
830 include : list or tuple, optional
810 A list of format type strings (MIME types) to include in the
831 A list of format type strings (MIME types) to include in the
811 format data dict. If this is set *only* the format types included
832 format data dict. If this is set *only* the format types included
812 in this list will be computed.
833 in this list will be computed.
813 exclude : list or tuple, optional
834 exclude : list or tuple, optional
814 A list of format type string (MIME types) to exclue in the format
835 A list of format type string (MIME types) to exclue in the format
815 data dict. If this is set all format types will be computed,
836 data dict. If this is set all format types will be computed,
816 except for those included in this argument.
837 except for those included in this argument.
817 """
838 """
818 from IPython.core.interactiveshell import InteractiveShell
839 from IPython.core.interactiveshell import InteractiveShell
819
840
820 InteractiveShell.instance().display_formatter.format(
841 InteractiveShell.instance().display_formatter.format(
821 obj,
842 obj,
822 include,
843 include,
823 exclude
844 exclude
824 )
845 )
825
846
@@ -1,143 +1,147 b''
1 """Implementation of magic functions for matplotlib/pylab support.
1 """Implementation of magic functions for matplotlib/pylab support.
2 """
2 """
3 from __future__ import print_function
3 from __future__ import print_function
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2012 The IPython Development Team.
5 # Copyright (c) 2012 The IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 # Our own packages
16 # Our own packages
17 from IPython.config.application import Application
17 from IPython.config.application import Application
18 from IPython.core import magic_arguments
18 from IPython.core import magic_arguments
19 from IPython.core.magic import Magics, magics_class, line_magic
19 from IPython.core.magic import Magics, magics_class, line_magic
20 from IPython.testing.skipdoctest import skip_doctest
20 from IPython.testing.skipdoctest import skip_doctest
21 from IPython.utils.warn import warn
21 from IPython.utils.warn import warn
22 from IPython.core.pylabtools import backends
22 from IPython.core.pylabtools import backends
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Magic implementation classes
25 # Magic implementation classes
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 magic_gui_arg = magic_arguments.argument(
28 magic_gui_arg = magic_arguments.argument(
29 'gui', nargs='?',
29 'gui', nargs='?',
30 help="""Name of the matplotlib backend to use %s.
30 help="""Name of the matplotlib backend to use %s.
31 If given, the corresponding matplotlib backend is used,
31 If given, the corresponding matplotlib backend is used,
32 otherwise it will be matplotlib's default
32 otherwise it will be matplotlib's default
33 (which you can set in your matplotlib config file).
33 (which you can set in your matplotlib config file).
34 """ % str(tuple(sorted(backends.keys())))
34 """ % str(tuple(sorted(backends.keys())))
35 )
35 )
36
36
37
37
38 @magics_class
38 @magics_class
39 class PylabMagics(Magics):
39 class PylabMagics(Magics):
40 """Magics related to matplotlib's pylab support"""
40 """Magics related to matplotlib's pylab support"""
41
41
42 @skip_doctest
42 @skip_doctest
43 @line_magic
43 @line_magic
44 @magic_arguments.magic_arguments()
44 @magic_arguments.magic_arguments()
45 @magic_gui_arg
45 @magic_gui_arg
46 def matplotlib(self, line=''):
46 def matplotlib(self, line=''):
47 """Set up matplotlib to work interactively.
47 """Set up matplotlib to work interactively.
48
48
49 This function lets you activate matplotlib interactive support
49 This function lets you activate matplotlib interactive support
50 at any point during an IPython session.
50 at any point during an IPython session. It does not import anything
51 It does not import anything into the interactive namespace.
51 into the interactive namespace.
52
52
53 If you are using the inline matplotlib backend for embedded figures,
53 If you are using the inline matplotlib backend in the IPython Notebook
54 you can adjust its behavior via the %config magic::
54 you can set which figure formats are enabled using the following::
55
55
56 # enable SVG figures, necessary for SVG+XHTML export in the qtconsole
56 In [1]: from IPython.display import set_matplotlib_formats
57 In [1]: %config InlineBackend.figure_format = 'svg'
57
58 In [2]: set_matplotlib_formats('pdf', 'svg')
58
59
59 # change the behavior of closing all figures at the end of each
60 See the docstring of `IPython.display.set_matplotlib_formats` and
60 # execution (cell), or allowing reuse of active figures across
61 `IPython.display.set_matplotlib_close` for more information on
61 # cells:
62 changing the behavior of the inline backend.
62 In [2]: %config InlineBackend.close_figures = False
63
63
64 Examples
64 Examples
65 --------
65 --------
66 In this case, where the MPL default is TkAgg::
66 To enable the inline backend for usage with the IPython Notebook::
67
68 In [1]: %matplotlib inline
69
70 In this case, where the matplotlib default is TkAgg::
67
71
68 In [2]: %matplotlib
72 In [2]: %matplotlib
69 Using matplotlib backend: TkAgg
73 Using matplotlib backend: TkAgg
70
74
71 But you can explicitly request a different backend::
75 But you can explicitly request a different GUI backend::
72
76
73 In [3]: %matplotlib qt
77 In [3]: %matplotlib qt
74 """
78 """
75 args = magic_arguments.parse_argstring(self.matplotlib, line)
79 args = magic_arguments.parse_argstring(self.matplotlib, line)
76 gui, backend = self.shell.enable_matplotlib(args.gui)
80 gui, backend = self.shell.enable_matplotlib(args.gui)
77 self._show_matplotlib_backend(args.gui, backend)
81 self._show_matplotlib_backend(args.gui, backend)
78
82
79 @skip_doctest
83 @skip_doctest
80 @line_magic
84 @line_magic
81 @magic_arguments.magic_arguments()
85 @magic_arguments.magic_arguments()
82 @magic_arguments.argument(
86 @magic_arguments.argument(
83 '--no-import-all', action='store_true', default=None,
87 '--no-import-all', action='store_true', default=None,
84 help="""Prevent IPython from performing ``import *`` into the interactive namespace.
88 help="""Prevent IPython from performing ``import *`` into the interactive namespace.
85
89
86 You can govern the default behavior of this flag with the
90 You can govern the default behavior of this flag with the
87 InteractiveShellApp.pylab_import_all configurable.
91 InteractiveShellApp.pylab_import_all configurable.
88 """
92 """
89 )
93 )
90 @magic_gui_arg
94 @magic_gui_arg
91 def pylab(self, line=''):
95 def pylab(self, line=''):
92 """Load numpy and matplotlib to work interactively.
96 """Load numpy and matplotlib to work interactively.
93
97
94 This function lets you activate pylab (matplotlib, numpy and
98 This function lets you activate pylab (matplotlib, numpy and
95 interactive support) at any point during an IPython session.
99 interactive support) at any point during an IPython session.
96
100
97 %pylab makes the following imports::
101 %pylab makes the following imports::
98
102
99 import numpy
103 import numpy
100 import matplotlib
104 import matplotlib
101 from matplotlib import pylab, mlab, pyplot
105 from matplotlib import pylab, mlab, pyplot
102 np = numpy
106 np = numpy
103 plt = pyplot
107 plt = pyplot
104
108
105 from IPython.display import display
109 from IPython.display import display
106 from IPython.core.pylabtools import figsize, getfigs
110 from IPython.core.pylabtools import figsize, getfigs
107
111
108 from pylab import *
112 from pylab import *
109 from numpy import *
113 from numpy import *
110
114
111 If you pass `--no-import-all`, the last two `*` imports will be excluded.
115 If you pass `--no-import-all`, the last two `*` imports will be excluded.
112
116
113 See the %matplotlib magic for more details about activating matplotlib
117 See the %matplotlib magic for more details about activating matplotlib
114 without affecting the interactive namespace.
118 without affecting the interactive namespace.
115 """
119 """
116 args = magic_arguments.parse_argstring(self.pylab, line)
120 args = magic_arguments.parse_argstring(self.pylab, line)
117 if args.no_import_all is None:
121 if args.no_import_all is None:
118 # get default from Application
122 # get default from Application
119 if Application.initialized():
123 if Application.initialized():
120 app = Application.instance()
124 app = Application.instance()
121 try:
125 try:
122 import_all = app.pylab_import_all
126 import_all = app.pylab_import_all
123 except AttributeError:
127 except AttributeError:
124 import_all = True
128 import_all = True
125 else:
129 else:
126 # nothing specified, no app - default True
130 # nothing specified, no app - default True
127 import_all = True
131 import_all = True
128 else:
132 else:
129 # invert no-import flag
133 # invert no-import flag
130 import_all = not args.no_import_all
134 import_all = not args.no_import_all
131
135
132 gui, backend, clobbered = self.shell.enable_pylab(args.gui, import_all=import_all)
136 gui, backend, clobbered = self.shell.enable_pylab(args.gui, import_all=import_all)
133 self._show_matplotlib_backend(args.gui, backend)
137 self._show_matplotlib_backend(args.gui, backend)
134 print ("Populating the interactive namespace from numpy and matplotlib")
138 print ("Populating the interactive namespace from numpy and matplotlib")
135 if clobbered:
139 if clobbered:
136 warn("pylab import has clobbered these variables: %s" % clobbered +
140 warn("pylab import has clobbered these variables: %s" % clobbered +
137 "\n`%pylab --no-import-all` prevents importing * from pylab and numpy"
141 "\n`%pylab --no-import-all` prevents importing * from pylab and numpy"
138 )
142 )
139
143
140 def _show_matplotlib_backend(self, gui, backend):
144 def _show_matplotlib_backend(self, gui, backend):
141 """show matplotlib message backend message"""
145 """show matplotlib message backend message"""
142 if not gui or gui == 'auto':
146 if not gui or gui == 'auto':
143 print("Using matplotlib backend: %s" % backend)
147 print("Using matplotlib backend: %s" % backend)
@@ -1,346 +1,358 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Pylab (matplotlib) support utilities.
2 """Pylab (matplotlib) support utilities.
3
3
4 Authors
4 Authors
5 -------
5 -------
6
6
7 * Fernando Perez.
7 * Fernando Perez.
8 * Brian Granger
8 * Brian Granger
9 """
9 """
10 from __future__ import print_function
10 from __future__ import print_function
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2009 The IPython Development Team
13 # Copyright (C) 2009 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import sys
23 import sys
24 from io import BytesIO
24 from io import BytesIO
25
25
26 from IPython.core.display import _pngxy
26 from IPython.core.display import _pngxy
27 from IPython.utils.decorators import flag_calls
27 from IPython.utils.decorators import flag_calls
28 from IPython.utils import py3compat
28
29
29 # If user specifies a GUI, that dictates the backend, otherwise we read the
30 # If user specifies a GUI, that dictates the backend, otherwise we read the
30 # user's mpl default from the mpl rc structure
31 # user's mpl default from the mpl rc structure
31 backends = {'tk': 'TkAgg',
32 backends = {'tk': 'TkAgg',
32 'gtk': 'GTKAgg',
33 'gtk': 'GTKAgg',
33 'gtk3': 'GTK3Agg',
34 'gtk3': 'GTK3Agg',
34 'wx': 'WXAgg',
35 'wx': 'WXAgg',
35 'qt': 'Qt4Agg', # qt3 not supported
36 'qt': 'Qt4Agg', # qt3 not supported
36 'qt4': 'Qt4Agg',
37 'qt4': 'Qt4Agg',
37 'osx': 'MacOSX',
38 'osx': 'MacOSX',
38 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
39 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'}
39
40
40 # We also need a reverse backends2guis mapping that will properly choose which
41 # We also need a reverse backends2guis mapping that will properly choose which
41 # GUI support to activate based on the desired matplotlib backend. For the
42 # GUI support to activate based on the desired matplotlib backend. For the
42 # most part it's just a reverse of the above dict, but we also need to add a
43 # most part it's just a reverse of the above dict, but we also need to add a
43 # few others that map to the same GUI manually:
44 # few others that map to the same GUI manually:
44 backend2gui = dict(zip(backends.values(), backends.keys()))
45 backend2gui = dict(zip(backends.values(), backends.keys()))
45 # Our tests expect backend2gui to just return 'qt'
46 # Our tests expect backend2gui to just return 'qt'
46 backend2gui['Qt4Agg'] = 'qt'
47 backend2gui['Qt4Agg'] = 'qt'
47 # In the reverse mapping, there are a few extra valid matplotlib backends that
48 # In the reverse mapping, there are a few extra valid matplotlib backends that
48 # map to the same GUI support
49 # map to the same GUI support
49 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
50 backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
50 backend2gui['GTK3Cairo'] = 'gtk3'
51 backend2gui['GTK3Cairo'] = 'gtk3'
51 backend2gui['WX'] = 'wx'
52 backend2gui['WX'] = 'wx'
52 backend2gui['CocoaAgg'] = 'osx'
53 backend2gui['CocoaAgg'] = 'osx'
53
54
54 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
55 # Matplotlib utilities
56 # Matplotlib utilities
56 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
57
58
58
59
59 def getfigs(*fig_nums):
60 def getfigs(*fig_nums):
60 """Get a list of matplotlib figures by figure numbers.
61 """Get a list of matplotlib figures by figure numbers.
61
62
62 If no arguments are given, all available figures are returned. If the
63 If no arguments are given, all available figures are returned. If the
63 argument list contains references to invalid figures, a warning is printed
64 argument list contains references to invalid figures, a warning is printed
64 but the function continues pasting further figures.
65 but the function continues pasting further figures.
65
66
66 Parameters
67 Parameters
67 ----------
68 ----------
68 figs : tuple
69 figs : tuple
69 A tuple of ints giving the figure numbers of the figures to return.
70 A tuple of ints giving the figure numbers of the figures to return.
70 """
71 """
71 from matplotlib._pylab_helpers import Gcf
72 from matplotlib._pylab_helpers import Gcf
72 if not fig_nums:
73 if not fig_nums:
73 fig_managers = Gcf.get_all_fig_managers()
74 fig_managers = Gcf.get_all_fig_managers()
74 return [fm.canvas.figure for fm in fig_managers]
75 return [fm.canvas.figure for fm in fig_managers]
75 else:
76 else:
76 figs = []
77 figs = []
77 for num in fig_nums:
78 for num in fig_nums:
78 f = Gcf.figs.get(num)
79 f = Gcf.figs.get(num)
79 if f is None:
80 if f is None:
80 print('Warning: figure %s not available.' % num)
81 print('Warning: figure %s not available.' % num)
81 else:
82 else:
82 figs.append(f.canvas.figure)
83 figs.append(f.canvas.figure)
83 return figs
84 return figs
84
85
85
86
86 def figsize(sizex, sizey):
87 def figsize(sizex, sizey):
87 """Set the default figure size to be [sizex, sizey].
88 """Set the default figure size to be [sizex, sizey].
88
89
89 This is just an easy to remember, convenience wrapper that sets::
90 This is just an easy to remember, convenience wrapper that sets::
90
91
91 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
92 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
92 """
93 """
93 import matplotlib
94 import matplotlib
94 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
95 matplotlib.rcParams['figure.figsize'] = [sizex, sizey]
95
96
96
97
97 def print_figure(fig, fmt='png', quality=90):
98 def print_figure(fig, fmt='png', quality=90):
98 """Convert a figure to svg, png or jpg for inline display.
99 """Convert a figure to svg, png or jpg for inline display.
99 Quality is only relevant for jpg.
100 Quality is only relevant for jpg.
100 """
101 """
101 from matplotlib import rcParams
102 from matplotlib import rcParams
102 # When there's an empty figure, we shouldn't return anything, otherwise we
103 # When there's an empty figure, we shouldn't return anything, otherwise we
103 # get big blank areas in the qt console.
104 # get big blank areas in the qt console.
104 if not fig.axes and not fig.lines:
105 if not fig.axes and not fig.lines:
105 return
106 return
106
107
107 fc = fig.get_facecolor()
108 fc = fig.get_facecolor()
108 ec = fig.get_edgecolor()
109 ec = fig.get_edgecolor()
109 bytes_io = BytesIO()
110 bytes_io = BytesIO()
110 dpi = rcParams['savefig.dpi']
111 dpi = rcParams['savefig.dpi']
111 if fmt == 'retina':
112 if fmt == 'retina':
112 dpi = dpi * 2
113 dpi = dpi * 2
113 fmt = 'png'
114 fmt = 'png'
114 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight',
115 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight',
115 facecolor=fc, edgecolor=ec, dpi=dpi, quality=quality)
116 facecolor=fc, edgecolor=ec, dpi=dpi, quality=quality)
116 data = bytes_io.getvalue()
117 data = bytes_io.getvalue()
117 return data
118 return data
118
119
119 def retina_figure(fig):
120 def retina_figure(fig):
120 """format a figure as a pixel-doubled (retina) PNG"""
121 """format a figure as a pixel-doubled (retina) PNG"""
121 pngdata = print_figure(fig, fmt='retina')
122 pngdata = print_figure(fig, fmt='retina')
122 w, h = _pngxy(pngdata)
123 w, h = _pngxy(pngdata)
123 metadata = dict(width=w//2, height=h//2)
124 metadata = dict(width=w//2, height=h//2)
124 return pngdata, metadata
125 return pngdata, metadata
125
126
126 # We need a little factory function here to create the closure where
127 # We need a little factory function here to create the closure where
127 # safe_execfile can live.
128 # safe_execfile can live.
128 def mpl_runner(safe_execfile):
129 def mpl_runner(safe_execfile):
129 """Factory to return a matplotlib-enabled runner for %run.
130 """Factory to return a matplotlib-enabled runner for %run.
130
131
131 Parameters
132 Parameters
132 ----------
133 ----------
133 safe_execfile : function
134 safe_execfile : function
134 This must be a function with the same interface as the
135 This must be a function with the same interface as the
135 :meth:`safe_execfile` method of IPython.
136 :meth:`safe_execfile` method of IPython.
136
137
137 Returns
138 Returns
138 -------
139 -------
139 A function suitable for use as the ``runner`` argument of the %run magic
140 A function suitable for use as the ``runner`` argument of the %run magic
140 function.
141 function.
141 """
142 """
142
143
143 def mpl_execfile(fname,*where,**kw):
144 def mpl_execfile(fname,*where,**kw):
144 """matplotlib-aware wrapper around safe_execfile.
145 """matplotlib-aware wrapper around safe_execfile.
145
146
146 Its interface is identical to that of the :func:`execfile` builtin.
147 Its interface is identical to that of the :func:`execfile` builtin.
147
148
148 This is ultimately a call to execfile(), but wrapped in safeties to
149 This is ultimately a call to execfile(), but wrapped in safeties to
149 properly handle interactive rendering."""
150 properly handle interactive rendering."""
150
151
151 import matplotlib
152 import matplotlib
152 import matplotlib.pylab as pylab
153 import matplotlib.pylab as pylab
153
154
154 #print '*** Matplotlib runner ***' # dbg
155 #print '*** Matplotlib runner ***' # dbg
155 # turn off rendering until end of script
156 # turn off rendering until end of script
156 is_interactive = matplotlib.rcParams['interactive']
157 is_interactive = matplotlib.rcParams['interactive']
157 matplotlib.interactive(False)
158 matplotlib.interactive(False)
158 safe_execfile(fname,*where,**kw)
159 safe_execfile(fname,*where,**kw)
159 matplotlib.interactive(is_interactive)
160 matplotlib.interactive(is_interactive)
160 # make rendering call now, if the user tried to do it
161 # make rendering call now, if the user tried to do it
161 if pylab.draw_if_interactive.called:
162 if pylab.draw_if_interactive.called:
162 pylab.draw()
163 pylab.draw()
163 pylab.draw_if_interactive.called = False
164 pylab.draw_if_interactive.called = False
164
165
165 return mpl_execfile
166 return mpl_execfile
166
167
167
168
168 def select_figure_format(shell, fmt, quality=90):
169 def select_figure_formats(shell, formats, quality=90):
169 """Select figure format for inline backend, can be 'png', 'retina', 'jpg', or 'svg'.
170 """Select figure formats for the inline backend.
170
171
171 Using this method ensures only one figure format is active at a time.
172 Parameters
173 ==========
174 shell : InteractiveShell
175 The main IPython instance.
176 formats : list
177 One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
178 quality : int
179 A percentage for the quality of JPEG figures.
172 """
180 """
173 from matplotlib.figure import Figure
181 from matplotlib.figure import Figure
174 from IPython.kernel.zmq.pylab import backend_inline
182 from IPython.kernel.zmq.pylab import backend_inline
175
183
176 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
184 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
177 png_formatter = shell.display_formatter.formatters['image/png']
185 png_formatter = shell.display_formatter.formatters['image/png']
178 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
186 jpg_formatter = shell.display_formatter.formatters['image/jpeg']
187 pdf_formatter = shell.display_formatter.formatters['application/pdf']
179
188
180 [ f.type_printers.pop(Figure, None) for f in {svg_formatter, png_formatter, jpg_formatter} ]
189 if isinstance(formats, py3compat.string_types):
190 formats = {formats}
181
191
182 if fmt == 'png':
192 [ f.type_printers.pop(Figure, None) for f in {svg_formatter, png_formatter, jpg_formatter} ]
183 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
184 elif fmt in ('png2x', 'retina'):
185 png_formatter.for_type(Figure, retina_figure)
186 elif fmt in ('jpg', 'jpeg'):
187 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', quality))
188 elif fmt == 'svg':
189 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
190 else:
191 raise ValueError("supported formats are: 'png', 'retina', 'svg', 'jpg', not %r" % fmt)
192
193
193 # set the format to be used in the backend()
194 for fmt in formats:
194 backend_inline._figure_format = fmt
195 if fmt == 'png':
196 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
197 elif fmt in ('png2x', 'retina'):
198 png_formatter.for_type(Figure, retina_figure)
199 elif fmt in ('jpg', 'jpeg'):
200 jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', quality))
201 elif fmt == 'svg':
202 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
203 elif fmt == 'pdf':
204 pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf'))
205 else:
206 raise ValueError("supported formats are: 'png', 'retina', 'svg', 'jpg', 'pdf' not %r" % fmt)
195
207
196 #-----------------------------------------------------------------------------
208 #-----------------------------------------------------------------------------
197 # Code for initializing matplotlib and importing pylab
209 # Code for initializing matplotlib and importing pylab
198 #-----------------------------------------------------------------------------
210 #-----------------------------------------------------------------------------
199
211
200
212
201 def find_gui_and_backend(gui=None, gui_select=None):
213 def find_gui_and_backend(gui=None, gui_select=None):
202 """Given a gui string return the gui and mpl backend.
214 """Given a gui string return the gui and mpl backend.
203
215
204 Parameters
216 Parameters
205 ----------
217 ----------
206 gui : str
218 gui : str
207 Can be one of ('tk','gtk','wx','qt','qt4','inline').
219 Can be one of ('tk','gtk','wx','qt','qt4','inline').
208 gui_select : str
220 gui_select : str
209 Can be one of ('tk','gtk','wx','qt','qt4','inline').
221 Can be one of ('tk','gtk','wx','qt','qt4','inline').
210 This is any gui already selected by the shell.
222 This is any gui already selected by the shell.
211
223
212 Returns
224 Returns
213 -------
225 -------
214 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
226 A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg',
215 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
227 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline').
216 """
228 """
217
229
218 import matplotlib
230 import matplotlib
219
231
220 if gui and gui != 'auto':
232 if gui and gui != 'auto':
221 # select backend based on requested gui
233 # select backend based on requested gui
222 backend = backends[gui]
234 backend = backends[gui]
223 else:
235 else:
224 # We need to read the backend from the original data structure, *not*
236 # We need to read the backend from the original data structure, *not*
225 # from mpl.rcParams, since a prior invocation of %matplotlib may have
237 # from mpl.rcParams, since a prior invocation of %matplotlib may have
226 # overwritten that.
238 # overwritten that.
227 # WARNING: this assumes matplotlib 1.1 or newer!!
239 # WARNING: this assumes matplotlib 1.1 or newer!!
228 backend = matplotlib.rcParamsOrig['backend']
240 backend = matplotlib.rcParamsOrig['backend']
229 # In this case, we need to find what the appropriate gui selection call
241 # In this case, we need to find what the appropriate gui selection call
230 # should be for IPython, so we can activate inputhook accordingly
242 # should be for IPython, so we can activate inputhook accordingly
231 gui = backend2gui.get(backend, None)
243 gui = backend2gui.get(backend, None)
232
244
233 # If we have already had a gui active, we need it and inline are the
245 # If we have already had a gui active, we need it and inline are the
234 # ones allowed.
246 # ones allowed.
235 if gui_select and gui != gui_select:
247 if gui_select and gui != gui_select:
236 gui = gui_select
248 gui = gui_select
237 backend = backends[gui]
249 backend = backends[gui]
238
250
239 return gui, backend
251 return gui, backend
240
252
241
253
242 def activate_matplotlib(backend):
254 def activate_matplotlib(backend):
243 """Activate the given backend and set interactive to True."""
255 """Activate the given backend and set interactive to True."""
244
256
245 import matplotlib
257 import matplotlib
246 matplotlib.interactive(True)
258 matplotlib.interactive(True)
247
259
248 # Matplotlib had a bug where even switch_backend could not force
260 # Matplotlib had a bug where even switch_backend could not force
249 # the rcParam to update. This needs to be set *before* the module
261 # the rcParam to update. This needs to be set *before* the module
250 # magic of switch_backend().
262 # magic of switch_backend().
251 matplotlib.rcParams['backend'] = backend
263 matplotlib.rcParams['backend'] = backend
252
264
253 import matplotlib.pyplot
265 import matplotlib.pyplot
254 matplotlib.pyplot.switch_backend(backend)
266 matplotlib.pyplot.switch_backend(backend)
255
267
256 # This must be imported last in the matplotlib series, after
268 # This must be imported last in the matplotlib series, after
257 # backend/interactivity choices have been made
269 # backend/interactivity choices have been made
258 import matplotlib.pylab as pylab
270 import matplotlib.pylab as pylab
259
271
260 pylab.show._needmain = False
272 pylab.show._needmain = False
261 # We need to detect at runtime whether show() is called by the user.
273 # We need to detect at runtime whether show() is called by the user.
262 # For this, we wrap it into a decorator which adds a 'called' flag.
274 # For this, we wrap it into a decorator which adds a 'called' flag.
263 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
275 pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
264
276
265
277
266 def import_pylab(user_ns, import_all=True):
278 def import_pylab(user_ns, import_all=True):
267 """Populate the namespace with pylab-related values.
279 """Populate the namespace with pylab-related values.
268
280
269 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
281 Imports matplotlib, pylab, numpy, and everything from pylab and numpy.
270
282
271 Also imports a few names from IPython (figsize, display, getfigs)
283 Also imports a few names from IPython (figsize, display, getfigs)
272
284
273 """
285 """
274
286
275 # Import numpy as np/pyplot as plt are conventions we're trying to
287 # Import numpy as np/pyplot as plt are conventions we're trying to
276 # somewhat standardize on. Making them available to users by default
288 # somewhat standardize on. Making them available to users by default
277 # will greatly help this.
289 # will greatly help this.
278 s = ("import numpy\n"
290 s = ("import numpy\n"
279 "import matplotlib\n"
291 "import matplotlib\n"
280 "from matplotlib import pylab, mlab, pyplot\n"
292 "from matplotlib import pylab, mlab, pyplot\n"
281 "np = numpy\n"
293 "np = numpy\n"
282 "plt = pyplot\n"
294 "plt = pyplot\n"
283 )
295 )
284 exec(s, user_ns)
296 exec(s, user_ns)
285
297
286 if import_all:
298 if import_all:
287 s = ("from matplotlib.pylab import *\n"
299 s = ("from matplotlib.pylab import *\n"
288 "from numpy import *\n")
300 "from numpy import *\n")
289 exec(s, user_ns)
301 exec(s, user_ns)
290
302
291 # IPython symbols to add
303 # IPython symbols to add
292 user_ns['figsize'] = figsize
304 user_ns['figsize'] = figsize
293 from IPython.core.display import display
305 from IPython.core.display import display
294 # Add display and getfigs to the user's namespace
306 # Add display and getfigs to the user's namespace
295 user_ns['display'] = display
307 user_ns['display'] = display
296 user_ns['getfigs'] = getfigs
308 user_ns['getfigs'] = getfigs
297
309
298
310
299 def configure_inline_support(shell, backend):
311 def configure_inline_support(shell, backend):
300 """Configure an IPython shell object for matplotlib use.
312 """Configure an IPython shell object for matplotlib use.
301
313
302 Parameters
314 Parameters
303 ----------
315 ----------
304 shell : InteractiveShell instance
316 shell : InteractiveShell instance
305
317
306 backend : matplotlib backend
318 backend : matplotlib backend
307 """
319 """
308 # If using our svg payload backend, register the post-execution
320 # If using our svg payload backend, register the post-execution
309 # function that will pick up the results for display. This can only be
321 # function that will pick up the results for display. This can only be
310 # done with access to the real shell object.
322 # done with access to the real shell object.
311
323
312 # Note: if we can't load the inline backend, then there's no point
324 # Note: if we can't load the inline backend, then there's no point
313 # continuing (such as in terminal-only shells in environments without
325 # continuing (such as in terminal-only shells in environments without
314 # zeromq available).
326 # zeromq available).
315 try:
327 try:
316 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
328 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
317 except ImportError:
329 except ImportError:
318 return
330 return
319 from matplotlib import pyplot
331 from matplotlib import pyplot
320
332
321 cfg = InlineBackend.instance(parent=shell)
333 cfg = InlineBackend.instance(parent=shell)
322 cfg.shell = shell
334 cfg.shell = shell
323 if cfg not in shell.configurables:
335 if cfg not in shell.configurables:
324 shell.configurables.append(cfg)
336 shell.configurables.append(cfg)
325
337
326 if backend == backends['inline']:
338 if backend == backends['inline']:
327 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
339 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
328 shell.register_post_execute(flush_figures)
340 shell.register_post_execute(flush_figures)
329
341
330 # Save rcParams that will be overwrittern
342 # Save rcParams that will be overwrittern
331 shell._saved_rcParams = dict()
343 shell._saved_rcParams = dict()
332 for k in cfg.rc:
344 for k in cfg.rc:
333 shell._saved_rcParams[k] = pyplot.rcParams[k]
345 shell._saved_rcParams[k] = pyplot.rcParams[k]
334 # load inline_rc
346 # load inline_rc
335 pyplot.rcParams.update(cfg.rc)
347 pyplot.rcParams.update(cfg.rc)
336 else:
348 else:
337 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
349 from IPython.kernel.zmq.pylab.backend_inline import flush_figures
338 if flush_figures in shell._post_execute:
350 if flush_figures in shell._post_execute:
339 shell._post_execute.pop(flush_figures)
351 shell._post_execute.pop(flush_figures)
340 if hasattr(shell, '_saved_rcParams'):
352 if hasattr(shell, '_saved_rcParams'):
341 pyplot.rcParams.update(shell._saved_rcParams)
353 pyplot.rcParams.update(shell._saved_rcParams)
342 del shell._saved_rcParams
354 del shell._saved_rcParams
343
355
344 # Setup the default figure format
356 # Setup the default figure format
345 select_figure_format(shell, cfg.figure_format, cfg.quality)
357 select_figure_formats(shell, cfg.figure_formats, cfg.quality)
346
358
@@ -1,282 +1,291 b''
1 """Tests for the Formatters."""
1 """Tests for the Formatters."""
2
2
3 from math import pi
3 from math import pi
4
4
5 try:
5 try:
6 import numpy
6 import numpy
7 except:
7 except:
8 numpy = None
8 numpy = None
9 import nose.tools as nt
9 import nose.tools as nt
10
10
11 from IPython.core.formatters import PlainTextFormatter, HTMLFormatter, _mod_name_key
11 from IPython.core.formatters import (
12 PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key
13 )
12 from IPython.utils.io import capture_output
14 from IPython.utils.io import capture_output
13
15
14 class A(object):
16 class A(object):
15 def __repr__(self):
17 def __repr__(self):
16 return 'A()'
18 return 'A()'
17
19
18 class B(A):
20 class B(A):
19 def __repr__(self):
21 def __repr__(self):
20 return 'B()'
22 return 'B()'
21
23
22 class C:
24 class C:
23 pass
25 pass
24
26
25 class BadPretty(object):
27 class BadPretty(object):
26 _repr_pretty_ = None
28 _repr_pretty_ = None
27
29
28 class GoodPretty(object):
30 class GoodPretty(object):
29 def _repr_pretty_(self, pp, cycle):
31 def _repr_pretty_(self, pp, cycle):
30 pp.text('foo')
32 pp.text('foo')
31
33
32 def __repr__(self):
34 def __repr__(self):
33 return 'GoodPretty()'
35 return 'GoodPretty()'
34
36
35 def foo_printer(obj, pp, cycle):
37 def foo_printer(obj, pp, cycle):
36 pp.text('foo')
38 pp.text('foo')
37
39
38 def test_pretty():
40 def test_pretty():
39 f = PlainTextFormatter()
41 f = PlainTextFormatter()
40 f.for_type(A, foo_printer)
42 f.for_type(A, foo_printer)
41 nt.assert_equal(f(A()), 'foo')
43 nt.assert_equal(f(A()), 'foo')
42 nt.assert_equal(f(B()), 'foo')
44 nt.assert_equal(f(B()), 'foo')
43 nt.assert_equal(f(GoodPretty()), 'foo')
45 nt.assert_equal(f(GoodPretty()), 'foo')
44 # Just don't raise an exception for the following:
46 # Just don't raise an exception for the following:
45 f(BadPretty())
47 f(BadPretty())
46
48
47 f.pprint = False
49 f.pprint = False
48 nt.assert_equal(f(A()), 'A()')
50 nt.assert_equal(f(A()), 'A()')
49 nt.assert_equal(f(B()), 'B()')
51 nt.assert_equal(f(B()), 'B()')
50 nt.assert_equal(f(GoodPretty()), 'GoodPretty()')
52 nt.assert_equal(f(GoodPretty()), 'GoodPretty()')
51
53
52
54
53 def test_deferred():
55 def test_deferred():
54 f = PlainTextFormatter()
56 f = PlainTextFormatter()
55
57
56 def test_precision():
58 def test_precision():
57 """test various values for float_precision."""
59 """test various values for float_precision."""
58 f = PlainTextFormatter()
60 f = PlainTextFormatter()
59 nt.assert_equal(f(pi), repr(pi))
61 nt.assert_equal(f(pi), repr(pi))
60 f.float_precision = 0
62 f.float_precision = 0
61 if numpy:
63 if numpy:
62 po = numpy.get_printoptions()
64 po = numpy.get_printoptions()
63 nt.assert_equal(po['precision'], 0)
65 nt.assert_equal(po['precision'], 0)
64 nt.assert_equal(f(pi), '3')
66 nt.assert_equal(f(pi), '3')
65 f.float_precision = 2
67 f.float_precision = 2
66 if numpy:
68 if numpy:
67 po = numpy.get_printoptions()
69 po = numpy.get_printoptions()
68 nt.assert_equal(po['precision'], 2)
70 nt.assert_equal(po['precision'], 2)
69 nt.assert_equal(f(pi), '3.14')
71 nt.assert_equal(f(pi), '3.14')
70 f.float_precision = '%g'
72 f.float_precision = '%g'
71 if numpy:
73 if numpy:
72 po = numpy.get_printoptions()
74 po = numpy.get_printoptions()
73 nt.assert_equal(po['precision'], 2)
75 nt.assert_equal(po['precision'], 2)
74 nt.assert_equal(f(pi), '3.14159')
76 nt.assert_equal(f(pi), '3.14159')
75 f.float_precision = '%e'
77 f.float_precision = '%e'
76 nt.assert_equal(f(pi), '3.141593e+00')
78 nt.assert_equal(f(pi), '3.141593e+00')
77 f.float_precision = ''
79 f.float_precision = ''
78 if numpy:
80 if numpy:
79 po = numpy.get_printoptions()
81 po = numpy.get_printoptions()
80 nt.assert_equal(po['precision'], 8)
82 nt.assert_equal(po['precision'], 8)
81 nt.assert_equal(f(pi), repr(pi))
83 nt.assert_equal(f(pi), repr(pi))
82
84
83 def test_bad_precision():
85 def test_bad_precision():
84 """test various invalid values for float_precision."""
86 """test various invalid values for float_precision."""
85 f = PlainTextFormatter()
87 f = PlainTextFormatter()
86 def set_fp(p):
88 def set_fp(p):
87 f.float_precision=p
89 f.float_precision=p
88 nt.assert_raises(ValueError, set_fp, '%')
90 nt.assert_raises(ValueError, set_fp, '%')
89 nt.assert_raises(ValueError, set_fp, '%.3f%i')
91 nt.assert_raises(ValueError, set_fp, '%.3f%i')
90 nt.assert_raises(ValueError, set_fp, 'foo')
92 nt.assert_raises(ValueError, set_fp, 'foo')
91 nt.assert_raises(ValueError, set_fp, -1)
93 nt.assert_raises(ValueError, set_fp, -1)
92
94
93 def test_for_type():
95 def test_for_type():
94 f = PlainTextFormatter()
96 f = PlainTextFormatter()
95
97
96 # initial return, None
98 # initial return, None
97 nt.assert_is(f.for_type(C, foo_printer), None)
99 nt.assert_is(f.for_type(C, foo_printer), None)
98 # no func queries
100 # no func queries
99 nt.assert_is(f.for_type(C), foo_printer)
101 nt.assert_is(f.for_type(C), foo_printer)
100 # shouldn't change anything
102 # shouldn't change anything
101 nt.assert_is(f.for_type(C), foo_printer)
103 nt.assert_is(f.for_type(C), foo_printer)
102 # None should do the same
104 # None should do the same
103 nt.assert_is(f.for_type(C, None), foo_printer)
105 nt.assert_is(f.for_type(C, None), foo_printer)
104 nt.assert_is(f.for_type(C, None), foo_printer)
106 nt.assert_is(f.for_type(C, None), foo_printer)
105
107
106 def test_for_type_string():
108 def test_for_type_string():
107 f = PlainTextFormatter()
109 f = PlainTextFormatter()
108
110
109 mod = C.__module__
111 mod = C.__module__
110
112
111 type_str = '%s.%s' % (C.__module__, 'C')
113 type_str = '%s.%s' % (C.__module__, 'C')
112
114
113 # initial return, None
115 # initial return, None
114 nt.assert_is(f.for_type(type_str, foo_printer), None)
116 nt.assert_is(f.for_type(type_str, foo_printer), None)
115 # no func queries
117 # no func queries
116 nt.assert_is(f.for_type(type_str), foo_printer)
118 nt.assert_is(f.for_type(type_str), foo_printer)
117 nt.assert_in(_mod_name_key(C), f.deferred_printers)
119 nt.assert_in(_mod_name_key(C), f.deferred_printers)
118 nt.assert_is(f.for_type(C), foo_printer)
120 nt.assert_is(f.for_type(C), foo_printer)
119 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
121 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
120 nt.assert_in(C, f.type_printers)
122 nt.assert_in(C, f.type_printers)
121
123
122 def test_for_type_by_name():
124 def test_for_type_by_name():
123 f = PlainTextFormatter()
125 f = PlainTextFormatter()
124
126
125 mod = C.__module__
127 mod = C.__module__
126
128
127 # initial return, None
129 # initial return, None
128 nt.assert_is(f.for_type_by_name(mod, 'C', foo_printer), None)
130 nt.assert_is(f.for_type_by_name(mod, 'C', foo_printer), None)
129 # no func queries
131 # no func queries
130 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
132 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
131 # shouldn't change anything
133 # shouldn't change anything
132 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
134 nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
133 # None should do the same
135 # None should do the same
134 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
136 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
135 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
137 nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
136
138
137 def test_lookup():
139 def test_lookup():
138 f = PlainTextFormatter()
140 f = PlainTextFormatter()
139
141
140 f.for_type(C, foo_printer)
142 f.for_type(C, foo_printer)
141 nt.assert_is(f.lookup(C()), foo_printer)
143 nt.assert_is(f.lookup(C()), foo_printer)
142 with nt.assert_raises(KeyError):
144 with nt.assert_raises(KeyError):
143 f.lookup(A())
145 f.lookup(A())
144
146
145 def test_lookup_string():
147 def test_lookup_string():
146 f = PlainTextFormatter()
148 f = PlainTextFormatter()
147 type_str = '%s.%s' % (C.__module__, 'C')
149 type_str = '%s.%s' % (C.__module__, 'C')
148
150
149 f.for_type(type_str, foo_printer)
151 f.for_type(type_str, foo_printer)
150 nt.assert_is(f.lookup(C()), foo_printer)
152 nt.assert_is(f.lookup(C()), foo_printer)
151 # should move from deferred to imported dict
153 # should move from deferred to imported dict
152 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
154 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
153 nt.assert_in(C, f.type_printers)
155 nt.assert_in(C, f.type_printers)
154
156
155 def test_lookup_by_type():
157 def test_lookup_by_type():
156 f = PlainTextFormatter()
158 f = PlainTextFormatter()
157 f.for_type(C, foo_printer)
159 f.for_type(C, foo_printer)
158 nt.assert_is(f.lookup_by_type(C), foo_printer)
160 nt.assert_is(f.lookup_by_type(C), foo_printer)
159 type_str = '%s.%s' % (C.__module__, 'C')
161 type_str = '%s.%s' % (C.__module__, 'C')
160 with nt.assert_raises(KeyError):
162 with nt.assert_raises(KeyError):
161 f.lookup_by_type(A)
163 f.lookup_by_type(A)
162
164
163 def test_lookup_by_type_string():
165 def test_lookup_by_type_string():
164 f = PlainTextFormatter()
166 f = PlainTextFormatter()
165 type_str = '%s.%s' % (C.__module__, 'C')
167 type_str = '%s.%s' % (C.__module__, 'C')
166 f.for_type(type_str, foo_printer)
168 f.for_type(type_str, foo_printer)
167
169
168 # verify insertion
170 # verify insertion
169 nt.assert_in(_mod_name_key(C), f.deferred_printers)
171 nt.assert_in(_mod_name_key(C), f.deferred_printers)
170 nt.assert_not_in(C, f.type_printers)
172 nt.assert_not_in(C, f.type_printers)
171
173
172 nt.assert_is(f.lookup_by_type(type_str), foo_printer)
174 nt.assert_is(f.lookup_by_type(type_str), foo_printer)
173 # lookup by string doesn't cause import
175 # lookup by string doesn't cause import
174 nt.assert_in(_mod_name_key(C), f.deferred_printers)
176 nt.assert_in(_mod_name_key(C), f.deferred_printers)
175 nt.assert_not_in(C, f.type_printers)
177 nt.assert_not_in(C, f.type_printers)
176
178
177 nt.assert_is(f.lookup_by_type(C), foo_printer)
179 nt.assert_is(f.lookup_by_type(C), foo_printer)
178 # should move from deferred to imported dict
180 # should move from deferred to imported dict
179 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
181 nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
180 nt.assert_in(C, f.type_printers)
182 nt.assert_in(C, f.type_printers)
181
183
182 def test_in_formatter():
184 def test_in_formatter():
183 f = PlainTextFormatter()
185 f = PlainTextFormatter()
184 f.for_type(C, foo_printer)
186 f.for_type(C, foo_printer)
185 type_str = '%s.%s' % (C.__module__, 'C')
187 type_str = '%s.%s' % (C.__module__, 'C')
186 nt.assert_in(C, f)
188 nt.assert_in(C, f)
187 nt.assert_in(type_str, f)
189 nt.assert_in(type_str, f)
188
190
189 def test_string_in_formatter():
191 def test_string_in_formatter():
190 f = PlainTextFormatter()
192 f = PlainTextFormatter()
191 type_str = '%s.%s' % (C.__module__, 'C')
193 type_str = '%s.%s' % (C.__module__, 'C')
192 f.for_type(type_str, foo_printer)
194 f.for_type(type_str, foo_printer)
193 nt.assert_in(type_str, f)
195 nt.assert_in(type_str, f)
194 nt.assert_in(C, f)
196 nt.assert_in(C, f)
195
197
196 def test_pop():
198 def test_pop():
197 f = PlainTextFormatter()
199 f = PlainTextFormatter()
198 f.for_type(C, foo_printer)
200 f.for_type(C, foo_printer)
199 nt.assert_is(f.lookup_by_type(C), foo_printer)
201 nt.assert_is(f.lookup_by_type(C), foo_printer)
200 nt.assert_is(f.pop(C, None), foo_printer)
202 nt.assert_is(f.pop(C, None), foo_printer)
201 f.for_type(C, foo_printer)
203 f.for_type(C, foo_printer)
202 nt.assert_is(f.pop(C), foo_printer)
204 nt.assert_is(f.pop(C), foo_printer)
203 with nt.assert_raises(KeyError):
205 with nt.assert_raises(KeyError):
204 f.lookup_by_type(C)
206 f.lookup_by_type(C)
205 with nt.assert_raises(KeyError):
207 with nt.assert_raises(KeyError):
206 f.pop(C)
208 f.pop(C)
207 with nt.assert_raises(KeyError):
209 with nt.assert_raises(KeyError):
208 f.pop(A)
210 f.pop(A)
209 nt.assert_is(f.pop(A, None), None)
211 nt.assert_is(f.pop(A, None), None)
210
212
211 def test_pop_string():
213 def test_pop_string():
212 f = PlainTextFormatter()
214 f = PlainTextFormatter()
213 type_str = '%s.%s' % (C.__module__, 'C')
215 type_str = '%s.%s' % (C.__module__, 'C')
214
216
215 with nt.assert_raises(KeyError):
217 with nt.assert_raises(KeyError):
216 f.pop(type_str)
218 f.pop(type_str)
217
219
218 f.for_type(type_str, foo_printer)
220 f.for_type(type_str, foo_printer)
219 f.pop(type_str)
221 f.pop(type_str)
220 with nt.assert_raises(KeyError):
222 with nt.assert_raises(KeyError):
221 f.lookup_by_type(C)
223 f.lookup_by_type(C)
222 with nt.assert_raises(KeyError):
224 with nt.assert_raises(KeyError):
223 f.pop(type_str)
225 f.pop(type_str)
224
226
225 f.for_type(C, foo_printer)
227 f.for_type(C, foo_printer)
226 nt.assert_is(f.pop(type_str, None), foo_printer)
228 nt.assert_is(f.pop(type_str, None), foo_printer)
227 with nt.assert_raises(KeyError):
229 with nt.assert_raises(KeyError):
228 f.lookup_by_type(C)
230 f.lookup_by_type(C)
229 with nt.assert_raises(KeyError):
231 with nt.assert_raises(KeyError):
230 f.pop(type_str)
232 f.pop(type_str)
231 nt.assert_is(f.pop(type_str, None), None)
233 nt.assert_is(f.pop(type_str, None), None)
232
234
233
235
234 def test_warn_error_method():
236 def test_warn_error_method():
235 f = HTMLFormatter()
237 f = HTMLFormatter()
236 class BadHTML(object):
238 class BadHTML(object):
237 def _repr_html_(self):
239 def _repr_html_(self):
238 return 1/0
240 return 1/0
239 bad = BadHTML()
241 bad = BadHTML()
240 with capture_output() as captured:
242 with capture_output() as captured:
241 result = f(bad)
243 result = f(bad)
242 nt.assert_is(result, None)
244 nt.assert_is(result, None)
243 nt.assert_in("FormatterWarning", captured.stderr)
245 nt.assert_in("FormatterWarning", captured.stderr)
244 nt.assert_in("text/html", captured.stderr)
246 nt.assert_in("text/html", captured.stderr)
245 nt.assert_in("zero", captured.stderr)
247 nt.assert_in("zero", captured.stderr)
246
248
247 def test_nowarn_notimplemented():
249 def test_nowarn_notimplemented():
248 f = HTMLFormatter()
250 f = HTMLFormatter()
249 class HTMLNotImplemented(object):
251 class HTMLNotImplemented(object):
250 def _repr_html_(self):
252 def _repr_html_(self):
251 raise NotImplementedError
253 raise NotImplementedError
252 return 1/0
254 return 1/0
253 h = HTMLNotImplemented()
255 h = HTMLNotImplemented()
254 with capture_output() as captured:
256 with capture_output() as captured:
255 result = f(h)
257 result = f(h)
256 nt.assert_is(result, None)
258 nt.assert_is(result, None)
257 nt.assert_not_in("FormatterWarning", captured.stderr)
259 nt.assert_not_in("FormatterWarning", captured.stderr)
258
260
259 def test_warn_error_for_type():
261 def test_warn_error_for_type():
260 f = HTMLFormatter()
262 f = HTMLFormatter()
261 f.for_type(int, lambda i: name_error)
263 f.for_type(int, lambda i: name_error)
262 with capture_output() as captured:
264 with capture_output() as captured:
263 result = f(5)
265 result = f(5)
264 nt.assert_is(result, None)
266 nt.assert_is(result, None)
265 nt.assert_in("FormatterWarning", captured.stderr)
267 nt.assert_in("FormatterWarning", captured.stderr)
266 nt.assert_in("text/html", captured.stderr)
268 nt.assert_in("text/html", captured.stderr)
267 nt.assert_in("name_error", captured.stderr)
269 nt.assert_in("name_error", captured.stderr)
268
270
269 def test_warn_error_pretty_method():
271 def test_warn_error_pretty_method():
270 f = PlainTextFormatter()
272 f = PlainTextFormatter()
271 class BadPretty(object):
273 class BadPretty(object):
272 def _repr_pretty_(self):
274 def _repr_pretty_(self):
273 return "hello"
275 return "hello"
274 bad = BadPretty()
276 bad = BadPretty()
275 with capture_output() as captured:
277 with capture_output() as captured:
276 result = f(bad)
278 result = f(bad)
277 nt.assert_is(result, None)
279 nt.assert_is(result, None)
278 nt.assert_in("FormatterWarning", captured.stderr)
280 nt.assert_in("FormatterWarning", captured.stderr)
279 nt.assert_in("text/plain", captured.stderr)
281 nt.assert_in("text/plain", captured.stderr)
280 nt.assert_in("argument", captured.stderr)
282 nt.assert_in("argument", captured.stderr)
281
283
284 class MakePDF(object):
285 def _repr_pdf_(self):
286 return 'PDF'
282
287
288 def test_pdf_formatter():
289 pdf = MakePDF()
290 f = PDFFormatter()
291 nt.assert_equal(f(pdf), 'PDF')
@@ -1,62 +1,62 b''
1 """Tornado handlers logging into the notebook.
1 """Tornado handlers logging into the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import uuid
19 import uuid
20
20
21 from tornado.escape import url_escape
21 from tornado.escape import url_escape
22
22
23 from IPython.lib.security import passwd_check
23 from IPython.lib.security import passwd_check
24
24
25 from ..base.handlers import IPythonHandler
25 from ..base.handlers import IPythonHandler
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Handler
28 # Handler
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 class LoginHandler(IPythonHandler):
31 class LoginHandler(IPythonHandler):
32
32
33 def _render(self, message=None):
33 def _render(self, message=None):
34 self.write(self.render_template('login.html',
34 self.write(self.render_template('login.html',
35 next=url_escape(self.get_argument('next', default=self.base_project_url)),
35 next=url_escape(self.get_argument('next', default=self.base_url)),
36 message=message,
36 message=message,
37 ))
37 ))
38
38
39 def get(self):
39 def get(self):
40 if self.current_user:
40 if self.current_user:
41 self.redirect(self.get_argument('next', default=self.base_project_url))
41 self.redirect(self.get_argument('next', default=self.base_url))
42 else:
42 else:
43 self._render()
43 self._render()
44
44
45 def post(self):
45 def post(self):
46 pwd = self.get_argument('password', default=u'')
46 pwd = self.get_argument('password', default=u'')
47 if self.login_available:
47 if self.login_available:
48 if passwd_check(self.password, pwd):
48 if passwd_check(self.password, pwd):
49 self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
49 self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
50 else:
50 else:
51 self._render(message={'error': 'Invalid password'})
51 self._render(message={'error': 'Invalid password'})
52 return
52 return
53
53
54 self.redirect(self.get_argument('next', default=self.base_project_url))
54 self.redirect(self.get_argument('next', default=self.base_url))
55
55
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # URL to handler mappings
58 # URL to handler mappings
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61
61
62 default_handlers = [(r"/login", LoginHandler)]
62 default_handlers = [(r"/login", LoginHandler)]
@@ -1,387 +1,387 b''
1 """Base Tornado handlers for the notebook.
1 """Base Tornado handlers for the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19
19
20 import functools
20 import functools
21 import json
21 import json
22 import logging
22 import logging
23 import os
23 import os
24 import re
24 import re
25 import sys
25 import sys
26 import traceback
26 import traceback
27 try:
27 try:
28 # py3
28 # py3
29 from http.client import responses
29 from http.client import responses
30 except ImportError:
30 except ImportError:
31 from httplib import responses
31 from httplib import responses
32
32
33 from jinja2 import TemplateNotFound
33 from jinja2 import TemplateNotFound
34 from tornado import web
34 from tornado import web
35
35
36 try:
36 try:
37 from tornado.log import app_log
37 from tornado.log import app_log
38 except ImportError:
38 except ImportError:
39 app_log = logging.getLogger()
39 app_log = logging.getLogger()
40
40
41 from IPython.config import Application
41 from IPython.config import Application
42 from IPython.utils.path import filefind
42 from IPython.utils.path import filefind
43 from IPython.utils.py3compat import string_types
43 from IPython.utils.py3compat import string_types
44 from IPython.html.utils import is_hidden
44 from IPython.html.utils import is_hidden
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Top-level handlers
47 # Top-level handlers
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 non_alphanum = re.compile(r'[^A-Za-z0-9]')
49 non_alphanum = re.compile(r'[^A-Za-z0-9]')
50
50
51 class AuthenticatedHandler(web.RequestHandler):
51 class AuthenticatedHandler(web.RequestHandler):
52 """A RequestHandler with an authenticated user."""
52 """A RequestHandler with an authenticated user."""
53
53
54 def clear_login_cookie(self):
54 def clear_login_cookie(self):
55 self.clear_cookie(self.cookie_name)
55 self.clear_cookie(self.cookie_name)
56
56
57 def get_current_user(self):
57 def get_current_user(self):
58 user_id = self.get_secure_cookie(self.cookie_name)
58 user_id = self.get_secure_cookie(self.cookie_name)
59 # For now the user_id should not return empty, but it could eventually
59 # For now the user_id should not return empty, but it could eventually
60 if user_id == '':
60 if user_id == '':
61 user_id = 'anonymous'
61 user_id = 'anonymous'
62 if user_id is None:
62 if user_id is None:
63 # prevent extra Invalid cookie sig warnings:
63 # prevent extra Invalid cookie sig warnings:
64 self.clear_login_cookie()
64 self.clear_login_cookie()
65 if not self.login_available:
65 if not self.login_available:
66 user_id = 'anonymous'
66 user_id = 'anonymous'
67 return user_id
67 return user_id
68
68
69 @property
69 @property
70 def cookie_name(self):
70 def cookie_name(self):
71 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
71 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
72 self.request.host
72 self.request.host
73 ))
73 ))
74 return self.settings.get('cookie_name', default_cookie_name)
74 return self.settings.get('cookie_name', default_cookie_name)
75
75
76 @property
76 @property
77 def password(self):
77 def password(self):
78 """our password"""
78 """our password"""
79 return self.settings.get('password', '')
79 return self.settings.get('password', '')
80
80
81 @property
81 @property
82 def logged_in(self):
82 def logged_in(self):
83 """Is a user currently logged in?
83 """Is a user currently logged in?
84
84
85 """
85 """
86 user = self.get_current_user()
86 user = self.get_current_user()
87 return (user and not user == 'anonymous')
87 return (user and not user == 'anonymous')
88
88
89 @property
89 @property
90 def login_available(self):
90 def login_available(self):
91 """May a user proceed to log in?
91 """May a user proceed to log in?
92
92
93 This returns True if login capability is available, irrespective of
93 This returns True if login capability is available, irrespective of
94 whether the user is already logged in or not.
94 whether the user is already logged in or not.
95
95
96 """
96 """
97 return bool(self.settings.get('password', ''))
97 return bool(self.settings.get('password', ''))
98
98
99
99
100 class IPythonHandler(AuthenticatedHandler):
100 class IPythonHandler(AuthenticatedHandler):
101 """IPython-specific extensions to authenticated handling
101 """IPython-specific extensions to authenticated handling
102
102
103 Mostly property shortcuts to IPython-specific settings.
103 Mostly property shortcuts to IPython-specific settings.
104 """
104 """
105
105
106 @property
106 @property
107 def config(self):
107 def config(self):
108 return self.settings.get('config', None)
108 return self.settings.get('config', None)
109
109
110 @property
110 @property
111 def log(self):
111 def log(self):
112 """use the IPython log by default, falling back on tornado's logger"""
112 """use the IPython log by default, falling back on tornado's logger"""
113 if Application.initialized():
113 if Application.initialized():
114 return Application.instance().log
114 return Application.instance().log
115 else:
115 else:
116 return app_log
116 return app_log
117
117
118 #---------------------------------------------------------------
118 #---------------------------------------------------------------
119 # URLs
119 # URLs
120 #---------------------------------------------------------------
120 #---------------------------------------------------------------
121
121
122 @property
122 @property
123 def ws_url(self):
123 def ws_url(self):
124 """websocket url matching the current request
124 """websocket url matching the current request
125
125
126 By default, this is just `''`, indicating that it should match
126 By default, this is just `''`, indicating that it should match
127 the same host, protocol, port, etc.
127 the same host, protocol, port, etc.
128 """
128 """
129 return self.settings.get('websocket_url', '')
129 return self.settings.get('websocket_url', '')
130
130
131 @property
131 @property
132 def mathjax_url(self):
132 def mathjax_url(self):
133 return self.settings.get('mathjax_url', '')
133 return self.settings.get('mathjax_url', '')
134
134
135 @property
135 @property
136 def base_project_url(self):
136 def base_url(self):
137 return self.settings.get('base_project_url', '/')
137 return self.settings.get('base_url', '/')
138
138
139 @property
139 @property
140 def base_kernel_url(self):
140 def base_kernel_url(self):
141 return self.settings.get('base_kernel_url', '/')
141 return self.settings.get('base_kernel_url', '/')
142
142
143 #---------------------------------------------------------------
143 #---------------------------------------------------------------
144 # Manager objects
144 # Manager objects
145 #---------------------------------------------------------------
145 #---------------------------------------------------------------
146
146
147 @property
147 @property
148 def kernel_manager(self):
148 def kernel_manager(self):
149 return self.settings['kernel_manager']
149 return self.settings['kernel_manager']
150
150
151 @property
151 @property
152 def notebook_manager(self):
152 def notebook_manager(self):
153 return self.settings['notebook_manager']
153 return self.settings['notebook_manager']
154
154
155 @property
155 @property
156 def cluster_manager(self):
156 def cluster_manager(self):
157 return self.settings['cluster_manager']
157 return self.settings['cluster_manager']
158
158
159 @property
159 @property
160 def session_manager(self):
160 def session_manager(self):
161 return self.settings['session_manager']
161 return self.settings['session_manager']
162
162
163 @property
163 @property
164 def project_dir(self):
164 def project_dir(self):
165 return self.notebook_manager.notebook_dir
165 return self.notebook_manager.notebook_dir
166
166
167 #---------------------------------------------------------------
167 #---------------------------------------------------------------
168 # template rendering
168 # template rendering
169 #---------------------------------------------------------------
169 #---------------------------------------------------------------
170
170
171 def get_template(self, name):
171 def get_template(self, name):
172 """Return the jinja template object for a given name"""
172 """Return the jinja template object for a given name"""
173 return self.settings['jinja2_env'].get_template(name)
173 return self.settings['jinja2_env'].get_template(name)
174
174
175 def render_template(self, name, **ns):
175 def render_template(self, name, **ns):
176 ns.update(self.template_namespace)
176 ns.update(self.template_namespace)
177 template = self.get_template(name)
177 template = self.get_template(name)
178 return template.render(**ns)
178 return template.render(**ns)
179
179
180 @property
180 @property
181 def template_namespace(self):
181 def template_namespace(self):
182 return dict(
182 return dict(
183 base_project_url=self.base_project_url,
183 base_url=self.base_url,
184 base_kernel_url=self.base_kernel_url,
184 base_kernel_url=self.base_kernel_url,
185 logged_in=self.logged_in,
185 logged_in=self.logged_in,
186 login_available=self.login_available,
186 login_available=self.login_available,
187 static_url=self.static_url,
187 static_url=self.static_url,
188 )
188 )
189
189
190 def get_json_body(self):
190 def get_json_body(self):
191 """Return the body of the request as JSON data."""
191 """Return the body of the request as JSON data."""
192 if not self.request.body:
192 if not self.request.body:
193 return None
193 return None
194 # Do we need to call body.decode('utf-8') here?
194 # Do we need to call body.decode('utf-8') here?
195 body = self.request.body.strip().decode(u'utf-8')
195 body = self.request.body.strip().decode(u'utf-8')
196 try:
196 try:
197 model = json.loads(body)
197 model = json.loads(body)
198 except Exception:
198 except Exception:
199 self.log.debug("Bad JSON: %r", body)
199 self.log.debug("Bad JSON: %r", body)
200 self.log.error("Couldn't parse JSON", exc_info=True)
200 self.log.error("Couldn't parse JSON", exc_info=True)
201 raise web.HTTPError(400, u'Invalid JSON in body of request')
201 raise web.HTTPError(400, u'Invalid JSON in body of request')
202 return model
202 return model
203
203
204 def get_error_html(self, status_code, **kwargs):
204 def get_error_html(self, status_code, **kwargs):
205 """render custom error pages"""
205 """render custom error pages"""
206 exception = kwargs.get('exception')
206 exception = kwargs.get('exception')
207 message = ''
207 message = ''
208 status_message = responses.get(status_code, 'Unknown HTTP Error')
208 status_message = responses.get(status_code, 'Unknown HTTP Error')
209 if exception:
209 if exception:
210 # get the custom message, if defined
210 # get the custom message, if defined
211 try:
211 try:
212 message = exception.log_message % exception.args
212 message = exception.log_message % exception.args
213 except Exception:
213 except Exception:
214 pass
214 pass
215
215
216 # construct the custom reason, if defined
216 # construct the custom reason, if defined
217 reason = getattr(exception, 'reason', '')
217 reason = getattr(exception, 'reason', '')
218 if reason:
218 if reason:
219 status_message = reason
219 status_message = reason
220
220
221 # build template namespace
221 # build template namespace
222 ns = dict(
222 ns = dict(
223 status_code=status_code,
223 status_code=status_code,
224 status_message=status_message,
224 status_message=status_message,
225 message=message,
225 message=message,
226 exception=exception,
226 exception=exception,
227 )
227 )
228
228
229 # render the template
229 # render the template
230 try:
230 try:
231 html = self.render_template('%s.html' % status_code, **ns)
231 html = self.render_template('%s.html' % status_code, **ns)
232 except TemplateNotFound:
232 except TemplateNotFound:
233 self.log.debug("No template for %d", status_code)
233 self.log.debug("No template for %d", status_code)
234 html = self.render_template('error.html', **ns)
234 html = self.render_template('error.html', **ns)
235 return html
235 return html
236
236
237
237
238 class Template404(IPythonHandler):
238 class Template404(IPythonHandler):
239 """Render our 404 template"""
239 """Render our 404 template"""
240 def prepare(self):
240 def prepare(self):
241 raise web.HTTPError(404)
241 raise web.HTTPError(404)
242
242
243
243
244 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
244 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
245 """static files should only be accessible when logged in"""
245 """static files should only be accessible when logged in"""
246
246
247 @web.authenticated
247 @web.authenticated
248 def get(self, path):
248 def get(self, path):
249 if os.path.splitext(path)[1] == '.ipynb':
249 if os.path.splitext(path)[1] == '.ipynb':
250 name = os.path.basename(path)
250 name = os.path.basename(path)
251 self.set_header('Content-Type', 'application/json')
251 self.set_header('Content-Type', 'application/json')
252 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
252 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
253
253
254 return web.StaticFileHandler.get(self, path)
254 return web.StaticFileHandler.get(self, path)
255
255
256 def compute_etag(self):
256 def compute_etag(self):
257 return None
257 return None
258
258
259 def validate_absolute_path(self, root, absolute_path):
259 def validate_absolute_path(self, root, absolute_path):
260 """Validate and return the absolute path.
260 """Validate and return the absolute path.
261
261
262 Requires tornado 3.1
262 Requires tornado 3.1
263
263
264 Adding to tornado's own handling, forbids the serving of hidden files.
264 Adding to tornado's own handling, forbids the serving of hidden files.
265 """
265 """
266 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
266 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
267 abs_root = os.path.abspath(root)
267 abs_root = os.path.abspath(root)
268 if is_hidden(abs_path, abs_root):
268 if is_hidden(abs_path, abs_root):
269 raise web.HTTPError(404)
269 raise web.HTTPError(404)
270 return abs_path
270 return abs_path
271
271
272
272
273 def json_errors(method):
273 def json_errors(method):
274 """Decorate methods with this to return GitHub style JSON errors.
274 """Decorate methods with this to return GitHub style JSON errors.
275
275
276 This should be used on any JSON API on any handler method that can raise HTTPErrors.
276 This should be used on any JSON API on any handler method that can raise HTTPErrors.
277
277
278 This will grab the latest HTTPError exception using sys.exc_info
278 This will grab the latest HTTPError exception using sys.exc_info
279 and then:
279 and then:
280
280
281 1. Set the HTTP status code based on the HTTPError
281 1. Set the HTTP status code based on the HTTPError
282 2. Create and return a JSON body with a message field describing
282 2. Create and return a JSON body with a message field describing
283 the error in a human readable form.
283 the error in a human readable form.
284 """
284 """
285 @functools.wraps(method)
285 @functools.wraps(method)
286 def wrapper(self, *args, **kwargs):
286 def wrapper(self, *args, **kwargs):
287 try:
287 try:
288 result = method(self, *args, **kwargs)
288 result = method(self, *args, **kwargs)
289 except web.HTTPError as e:
289 except web.HTTPError as e:
290 status = e.status_code
290 status = e.status_code
291 message = e.log_message
291 message = e.log_message
292 self.set_status(e.status_code)
292 self.set_status(e.status_code)
293 self.finish(json.dumps(dict(message=message)))
293 self.finish(json.dumps(dict(message=message)))
294 except Exception:
294 except Exception:
295 self.log.error("Unhandled error in API request", exc_info=True)
295 self.log.error("Unhandled error in API request", exc_info=True)
296 status = 500
296 status = 500
297 message = "Unknown server error"
297 message = "Unknown server error"
298 t, value, tb = sys.exc_info()
298 t, value, tb = sys.exc_info()
299 self.set_status(status)
299 self.set_status(status)
300 tb_text = ''.join(traceback.format_exception(t, value, tb))
300 tb_text = ''.join(traceback.format_exception(t, value, tb))
301 reply = dict(message=message, traceback=tb_text)
301 reply = dict(message=message, traceback=tb_text)
302 self.finish(json.dumps(reply))
302 self.finish(json.dumps(reply))
303 else:
303 else:
304 return result
304 return result
305 return wrapper
305 return wrapper
306
306
307
307
308
308
309 #-----------------------------------------------------------------------------
309 #-----------------------------------------------------------------------------
310 # File handler
310 # File handler
311 #-----------------------------------------------------------------------------
311 #-----------------------------------------------------------------------------
312
312
313 # to minimize subclass changes:
313 # to minimize subclass changes:
314 HTTPError = web.HTTPError
314 HTTPError = web.HTTPError
315
315
316 class FileFindHandler(web.StaticFileHandler):
316 class FileFindHandler(web.StaticFileHandler):
317 """subclass of StaticFileHandler for serving files from a search path"""
317 """subclass of StaticFileHandler for serving files from a search path"""
318
318
319 # cache search results, don't search for files more than once
319 # cache search results, don't search for files more than once
320 _static_paths = {}
320 _static_paths = {}
321
321
322 def initialize(self, path, default_filename=None):
322 def initialize(self, path, default_filename=None):
323 if isinstance(path, string_types):
323 if isinstance(path, string_types):
324 path = [path]
324 path = [path]
325
325
326 self.root = tuple(
326 self.root = tuple(
327 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
327 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
328 )
328 )
329 self.default_filename = default_filename
329 self.default_filename = default_filename
330
330
331 def compute_etag(self):
331 def compute_etag(self):
332 return None
332 return None
333
333
334 @classmethod
334 @classmethod
335 def get_absolute_path(cls, roots, path):
335 def get_absolute_path(cls, roots, path):
336 """locate a file to serve on our static file search path"""
336 """locate a file to serve on our static file search path"""
337 with cls._lock:
337 with cls._lock:
338 if path in cls._static_paths:
338 if path in cls._static_paths:
339 return cls._static_paths[path]
339 return cls._static_paths[path]
340 try:
340 try:
341 abspath = os.path.abspath(filefind(path, roots))
341 abspath = os.path.abspath(filefind(path, roots))
342 except IOError:
342 except IOError:
343 # IOError means not found
343 # IOError means not found
344 return ''
344 return ''
345
345
346 cls._static_paths[path] = abspath
346 cls._static_paths[path] = abspath
347 return abspath
347 return abspath
348
348
349 def validate_absolute_path(self, root, absolute_path):
349 def validate_absolute_path(self, root, absolute_path):
350 """check if the file should be served (raises 404, 403, etc.)"""
350 """check if the file should be served (raises 404, 403, etc.)"""
351 if absolute_path == '':
351 if absolute_path == '':
352 raise web.HTTPError(404)
352 raise web.HTTPError(404)
353
353
354 for root in self.root:
354 for root in self.root:
355 if (absolute_path + os.sep).startswith(root):
355 if (absolute_path + os.sep).startswith(root):
356 break
356 break
357
357
358 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
358 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
359
359
360
360
361 class TrailingSlashHandler(web.RequestHandler):
361 class TrailingSlashHandler(web.RequestHandler):
362 """Simple redirect handler that strips trailing slashes
362 """Simple redirect handler that strips trailing slashes
363
363
364 This should be the first, highest priority handler.
364 This should be the first, highest priority handler.
365 """
365 """
366
366
367 SUPPORTED_METHODS = ['GET']
367 SUPPORTED_METHODS = ['GET']
368
368
369 def get(self):
369 def get(self):
370 self.redirect(self.request.uri.rstrip('/'))
370 self.redirect(self.request.uri.rstrip('/'))
371
371
372 #-----------------------------------------------------------------------------
372 #-----------------------------------------------------------------------------
373 # URL pattern fragments for re-use
373 # URL pattern fragments for re-use
374 #-----------------------------------------------------------------------------
374 #-----------------------------------------------------------------------------
375
375
376 path_regex = r"(?P<path>(?:/.*)*)"
376 path_regex = r"(?P<path>(?:/.*)*)"
377 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
377 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
378 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
378 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
379
379
380 #-----------------------------------------------------------------------------
380 #-----------------------------------------------------------------------------
381 # URL to handler mappings
381 # URL to handler mappings
382 #-----------------------------------------------------------------------------
382 #-----------------------------------------------------------------------------
383
383
384
384
385 default_handlers = [
385 default_handlers = [
386 (r".*/", TrailingSlashHandler)
386 (r".*/", TrailingSlashHandler)
387 ]
387 ]
@@ -1,90 +1,90 b''
1 """Tornado handlers for the live notebook view.
1 """Tornado handlers for the live notebook view.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 from tornado import web
20 from tornado import web
21 HTTPError = web.HTTPError
21 HTTPError = web.HTTPError
22
22
23 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
23 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
24 from ..utils import url_path_join, url_escape
24 from ..utils import url_path_join, url_escape
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Handlers
27 # Handlers
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30
30
31 class NotebookHandler(IPythonHandler):
31 class NotebookHandler(IPythonHandler):
32
32
33 @web.authenticated
33 @web.authenticated
34 def get(self, path='', name=None):
34 def get(self, path='', name=None):
35 """get renders the notebook template if a name is given, or
35 """get renders the notebook template if a name is given, or
36 redirects to the '/files/' handler if the name is not given."""
36 redirects to the '/files/' handler if the name is not given."""
37 path = path.strip('/')
37 path = path.strip('/')
38 nbm = self.notebook_manager
38 nbm = self.notebook_manager
39 if name is None:
39 if name is None:
40 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
40 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
41
41
42 # a .ipynb filename was given
42 # a .ipynb filename was given
43 if not nbm.notebook_exists(name, path):
43 if not nbm.notebook_exists(name, path):
44 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
44 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
45 name = url_escape(name)
45 name = url_escape(name)
46 path = url_escape(path)
46 path = url_escape(path)
47 self.write(self.render_template('notebook.html',
47 self.write(self.render_template('notebook.html',
48 project=self.project_dir,
48 project=self.project_dir,
49 notebook_path=path,
49 notebook_path=path,
50 notebook_name=name,
50 notebook_name=name,
51 kill_kernel=False,
51 kill_kernel=False,
52 mathjax_url=self.mathjax_url,
52 mathjax_url=self.mathjax_url,
53 )
53 )
54 )
54 )
55
55
56 class NotebookRedirectHandler(IPythonHandler):
56 class NotebookRedirectHandler(IPythonHandler):
57 def get(self, path=''):
57 def get(self, path=''):
58 nbm = self.notebook_manager
58 nbm = self.notebook_manager
59 if nbm.path_exists(path):
59 if nbm.path_exists(path):
60 # it's a *directory*, redirect to /tree
60 # it's a *directory*, redirect to /tree
61 url = url_path_join(self.base_project_url, 'tree', path)
61 url = url_path_join(self.base_url, 'tree', path)
62 else:
62 else:
63 # otherwise, redirect to /files
63 # otherwise, redirect to /files
64 if '/files/' in path:
64 if '/files/' in path:
65 # redirect without files/ iff it would 404
65 # redirect without files/ iff it would 404
66 # this preserves pre-2.0-style 'files/' links
66 # this preserves pre-2.0-style 'files/' links
67 # FIXME: this is hardcoded based on notebook_path,
67 # FIXME: this is hardcoded based on notebook_path,
68 # but so is the files handler itself,
68 # but so is the files handler itself,
69 # so it should work until both are cleaned up.
69 # so it should work until both are cleaned up.
70 parts = path.split('/')
70 parts = path.split('/')
71 files_path = os.path.join(nbm.notebook_dir, *parts)
71 files_path = os.path.join(nbm.notebook_dir, *parts)
72 self.log.warn("filespath: %s", files_path)
72 self.log.warn("filespath: %s", files_path)
73 if not os.path.exists(files_path):
73 if not os.path.exists(files_path):
74 path = path.replace('/files/', '/', 1)
74 path = path.replace('/files/', '/', 1)
75
75
76 url = url_path_join(self.base_project_url, 'files', path)
76 url = url_path_join(self.base_url, 'files', path)
77 url = url_escape(url)
77 url = url_escape(url)
78 self.log.debug("Redirecting %s to %s", self.request.path, url)
78 self.log.debug("Redirecting %s to %s", self.request.path, url)
79 self.redirect(url)
79 self.redirect(url)
80
80
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 # URL to handler mappings
82 # URL to handler mappings
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84
84
85
85
86 default_handlers = [
86 default_handlers = [
87 (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
87 (r"/notebooks%s" % notebook_path_regex, NotebookHandler),
88 (r"/notebooks%s" % path_regex, NotebookRedirectHandler),
88 (r"/notebooks%s" % path_regex, NotebookRedirectHandler),
89 ]
89 ]
90
90
@@ -1,837 +1,842 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 from __future__ import print_function
8 from __future__ import print_function
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 # stdlib
20 # stdlib
21 import errno
21 import errno
22 import io
22 import io
23 import json
23 import json
24 import logging
24 import logging
25 import os
25 import os
26 import random
26 import random
27 import select
27 import select
28 import signal
28 import signal
29 import socket
29 import socket
30 import sys
30 import sys
31 import threading
31 import threading
32 import time
32 import time
33 import webbrowser
33 import webbrowser
34
34
35
35
36 # Third party
36 # Third party
37 # check for pyzmq 2.1.11
37 # check for pyzmq 2.1.11
38 from IPython.utils.zmqrelated import check_for_zmq
38 from IPython.utils.zmqrelated import check_for_zmq
39 check_for_zmq('2.1.11', 'IPython.html')
39 check_for_zmq('2.1.11', 'IPython.html')
40
40
41 from jinja2 import Environment, FileSystemLoader
41 from jinja2 import Environment, FileSystemLoader
42
42
43 # Install the pyzmq ioloop. This has to be done before anything else from
43 # Install the pyzmq ioloop. This has to be done before anything else from
44 # tornado is imported.
44 # tornado is imported.
45 from zmq.eventloop import ioloop
45 from zmq.eventloop import ioloop
46 ioloop.install()
46 ioloop.install()
47
47
48 # check for tornado 3.1.0
48 # check for tornado 3.1.0
49 msg = "The IPython Notebook requires tornado >= 3.1.0"
49 msg = "The IPython Notebook requires tornado >= 3.1.0"
50 try:
50 try:
51 import tornado
51 import tornado
52 except ImportError:
52 except ImportError:
53 raise ImportError(msg)
53 raise ImportError(msg)
54 try:
54 try:
55 version_info = tornado.version_info
55 version_info = tornado.version_info
56 except AttributeError:
56 except AttributeError:
57 raise ImportError(msg + ", but you have < 1.1.0")
57 raise ImportError(msg + ", but you have < 1.1.0")
58 if version_info < (3,1,0):
58 if version_info < (3,1,0):
59 raise ImportError(msg + ", but you have %s" % tornado.version)
59 raise ImportError(msg + ", but you have %s" % tornado.version)
60
60
61 from tornado import httpserver
61 from tornado import httpserver
62 from tornado import web
62 from tornado import web
63
63
64 # Our own libraries
64 # Our own libraries
65 from IPython.html import DEFAULT_STATIC_FILES_PATH
65 from IPython.html import DEFAULT_STATIC_FILES_PATH
66 from .base.handlers import Template404
66 from .base.handlers import Template404
67 from .log import log_request
67 from .log import log_request
68 from .services.kernels.kernelmanager import MappingKernelManager
68 from .services.kernels.kernelmanager import MappingKernelManager
69 from .services.notebooks.nbmanager import NotebookManager
69 from .services.notebooks.nbmanager import NotebookManager
70 from .services.notebooks.filenbmanager import FileNotebookManager
70 from .services.notebooks.filenbmanager import FileNotebookManager
71 from .services.clusters.clustermanager import ClusterManager
71 from .services.clusters.clustermanager import ClusterManager
72 from .services.sessions.sessionmanager import SessionManager
72 from .services.sessions.sessionmanager import SessionManager
73
73
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
75
75
76 from IPython.config.application import catch_config_error, boolean_flag
76 from IPython.config.application import catch_config_error, boolean_flag
77 from IPython.core.application import BaseIPythonApplication
77 from IPython.core.application import BaseIPythonApplication
78 from IPython.core.profiledir import ProfileDir
78 from IPython.core.profiledir import ProfileDir
79 from IPython.consoleapp import IPythonConsoleApp
79 from IPython.consoleapp import IPythonConsoleApp
80 from IPython.kernel import swallow_argv
80 from IPython.kernel import swallow_argv
81 from IPython.kernel.zmq.session import default_secure
81 from IPython.kernel.zmq.session import default_secure
82 from IPython.kernel.zmq.kernelapp import (
82 from IPython.kernel.zmq.kernelapp import (
83 kernel_flags,
83 kernel_flags,
84 kernel_aliases,
84 kernel_aliases,
85 )
85 )
86 from IPython.utils.importstring import import_item
86 from IPython.utils.importstring import import_item
87 from IPython.utils.localinterfaces import localhost
87 from IPython.utils.localinterfaces import localhost
88 from IPython.utils import submodule
88 from IPython.utils import submodule
89 from IPython.utils.traitlets import (
89 from IPython.utils.traitlets import (
90 Dict, Unicode, Integer, List, Bool, Bytes,
90 Dict, Unicode, Integer, List, Bool, Bytes,
91 DottedObjectName
91 DottedObjectName
92 )
92 )
93 from IPython.utils import py3compat
93 from IPython.utils import py3compat
94 from IPython.utils.path import filefind, get_ipython_dir
94 from IPython.utils.path import filefind, get_ipython_dir
95
95
96 from .utils import url_path_join
96 from .utils import url_path_join
97
97
98 #-----------------------------------------------------------------------------
98 #-----------------------------------------------------------------------------
99 # Module globals
99 # Module globals
100 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
101
101
102 _examples = """
102 _examples = """
103 ipython notebook # start the notebook
103 ipython notebook # start the notebook
104 ipython notebook --profile=sympy # use the sympy profile
104 ipython notebook --profile=sympy # use the sympy profile
105 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
105 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
106 """
106 """
107
107
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109 # Helper functions
109 # Helper functions
110 #-----------------------------------------------------------------------------
110 #-----------------------------------------------------------------------------
111
111
112 def random_ports(port, n):
112 def random_ports(port, n):
113 """Generate a list of n random ports near the given port.
113 """Generate a list of n random ports near the given port.
114
114
115 The first 5 ports will be sequential, and the remaining n-5 will be
115 The first 5 ports will be sequential, and the remaining n-5 will be
116 randomly selected in the range [port-2*n, port+2*n].
116 randomly selected in the range [port-2*n, port+2*n].
117 """
117 """
118 for i in range(min(5, n)):
118 for i in range(min(5, n)):
119 yield port + i
119 yield port + i
120 for i in range(n-5):
120 for i in range(n-5):
121 yield max(1, port + random.randint(-2*n, 2*n))
121 yield max(1, port + random.randint(-2*n, 2*n))
122
122
123 def load_handlers(name):
123 def load_handlers(name):
124 """Load the (URL pattern, handler) tuples for each component."""
124 """Load the (URL pattern, handler) tuples for each component."""
125 name = 'IPython.html.' + name
125 name = 'IPython.html.' + name
126 mod = __import__(name, fromlist=['default_handlers'])
126 mod = __import__(name, fromlist=['default_handlers'])
127 return mod.default_handlers
127 return mod.default_handlers
128
128
129 #-----------------------------------------------------------------------------
129 #-----------------------------------------------------------------------------
130 # The Tornado web application
130 # The Tornado web application
131 #-----------------------------------------------------------------------------
131 #-----------------------------------------------------------------------------
132
132
133 class NotebookWebApplication(web.Application):
133 class NotebookWebApplication(web.Application):
134
134
135 def __init__(self, ipython_app, kernel_manager, notebook_manager,
135 def __init__(self, ipython_app, kernel_manager, notebook_manager,
136 cluster_manager, session_manager, log, base_project_url,
136 cluster_manager, session_manager, log, base_url,
137 settings_overrides):
137 settings_overrides):
138
138
139 settings = self.init_settings(
139 settings = self.init_settings(
140 ipython_app, kernel_manager, notebook_manager, cluster_manager,
140 ipython_app, kernel_manager, notebook_manager, cluster_manager,
141 session_manager, log, base_project_url, settings_overrides)
141 session_manager, log, base_url, settings_overrides)
142 handlers = self.init_handlers(settings)
142 handlers = self.init_handlers(settings)
143
143
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145
145
146 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
146 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
147 cluster_manager, session_manager, log, base_project_url,
147 cluster_manager, session_manager, log, base_url,
148 settings_overrides):
148 settings_overrides):
149 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
149 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
150 # base_project_url will always be unicode, which will in turn
150 # base_url will always be unicode, which will in turn
151 # make the patterns unicode, and ultimately result in unicode
151 # make the patterns unicode, and ultimately result in unicode
152 # keys in kwargs to handler._execute(**kwargs) in tornado.
152 # keys in kwargs to handler._execute(**kwargs) in tornado.
153 # This enforces that base_project_url be ascii in that situation.
153 # This enforces that base_url be ascii in that situation.
154 #
154 #
155 # Note that the URLs these patterns check against are escaped,
155 # Note that the URLs these patterns check against are escaped,
156 # and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'.
156 # and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'.
157 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
157 base_url = py3compat.unicode_to_str(base_url, 'ascii')
158 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
158 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
159 settings = dict(
159 settings = dict(
160 # basics
160 # basics
161 log_function=log_request,
161 log_function=log_request,
162 base_project_url=base_project_url,
162 base_url=base_url,
163 base_kernel_url=ipython_app.base_kernel_url,
163 base_kernel_url=ipython_app.base_kernel_url,
164 template_path=template_path,
164 template_path=template_path,
165 static_path=ipython_app.static_file_path,
165 static_path=ipython_app.static_file_path,
166 static_handler_class = FileFindHandler,
166 static_handler_class = FileFindHandler,
167 static_url_prefix = url_path_join(base_project_url,'/static/'),
167 static_url_prefix = url_path_join(base_url,'/static/'),
168
168
169 # authentication
169 # authentication
170 cookie_secret=ipython_app.cookie_secret,
170 cookie_secret=ipython_app.cookie_secret,
171 login_url=url_path_join(base_project_url,'/login'),
171 login_url=url_path_join(base_url,'/login'),
172 password=ipython_app.password,
172 password=ipython_app.password,
173
173
174 # managers
174 # managers
175 kernel_manager=kernel_manager,
175 kernel_manager=kernel_manager,
176 notebook_manager=notebook_manager,
176 notebook_manager=notebook_manager,
177 cluster_manager=cluster_manager,
177 cluster_manager=cluster_manager,
178 session_manager=session_manager,
178 session_manager=session_manager,
179
179
180 # IPython stuff
180 # IPython stuff
181 nbextensions_path = ipython_app.nbextensions_path,
181 nbextensions_path = ipython_app.nbextensions_path,
182 mathjax_url=ipython_app.mathjax_url,
182 mathjax_url=ipython_app.mathjax_url,
183 config=ipython_app.config,
183 config=ipython_app.config,
184 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
184 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
185 )
185 )
186
186
187 # allow custom overrides for the tornado web app.
187 # allow custom overrides for the tornado web app.
188 settings.update(settings_overrides)
188 settings.update(settings_overrides)
189 return settings
189 return settings
190
190
191 def init_handlers(self, settings):
191 def init_handlers(self, settings):
192 # Load the (URL pattern, handler) tuples for each component.
192 # Load the (URL pattern, handler) tuples for each component.
193 handlers = []
193 handlers = []
194 handlers.extend(load_handlers('base.handlers'))
194 handlers.extend(load_handlers('base.handlers'))
195 handlers.extend(load_handlers('tree.handlers'))
195 handlers.extend(load_handlers('tree.handlers'))
196 handlers.extend(load_handlers('auth.login'))
196 handlers.extend(load_handlers('auth.login'))
197 handlers.extend(load_handlers('auth.logout'))
197 handlers.extend(load_handlers('auth.logout'))
198 handlers.extend(load_handlers('notebook.handlers'))
198 handlers.extend(load_handlers('notebook.handlers'))
199 handlers.extend(load_handlers('nbconvert.handlers'))
199 handlers.extend(load_handlers('nbconvert.handlers'))
200 handlers.extend(load_handlers('services.kernels.handlers'))
200 handlers.extend(load_handlers('services.kernels.handlers'))
201 handlers.extend(load_handlers('services.notebooks.handlers'))
201 handlers.extend(load_handlers('services.notebooks.handlers'))
202 handlers.extend(load_handlers('services.clusters.handlers'))
202 handlers.extend(load_handlers('services.clusters.handlers'))
203 handlers.extend(load_handlers('services.sessions.handlers'))
203 handlers.extend(load_handlers('services.sessions.handlers'))
204 handlers.extend(load_handlers('services.nbconvert.handlers'))
204 handlers.extend(load_handlers('services.nbconvert.handlers'))
205 handlers.extend([
205 handlers.extend([
206 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
206 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
207 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
207 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
208 ])
208 ])
209 # prepend base_project_url onto the patterns that we match
209 # prepend base_url onto the patterns that we match
210 new_handlers = []
210 new_handlers = []
211 for handler in handlers:
211 for handler in handlers:
212 pattern = url_path_join(settings['base_project_url'], handler[0])
212 pattern = url_path_join(settings['base_url'], handler[0])
213 new_handler = tuple([pattern] + list(handler[1:]))
213 new_handler = tuple([pattern] + list(handler[1:]))
214 new_handlers.append(new_handler)
214 new_handlers.append(new_handler)
215 # add 404 on the end, which will catch everything that falls through
215 # add 404 on the end, which will catch everything that falls through
216 new_handlers.append((r'(.*)', Template404))
216 new_handlers.append((r'(.*)', Template404))
217 return new_handlers
217 return new_handlers
218
218
219
219
220 class NbserverListApp(BaseIPythonApplication):
220 class NbserverListApp(BaseIPythonApplication):
221
221
222 description="List currently running notebook servers in this profile."
222 description="List currently running notebook servers in this profile."
223
223
224 flags = dict(
224 flags = dict(
225 json=({'NbserverListApp': {'json': True}},
225 json=({'NbserverListApp': {'json': True}},
226 "Produce machine-readable JSON output."),
226 "Produce machine-readable JSON output."),
227 )
227 )
228
228
229 json = Bool(False, config=True,
229 json = Bool(False, config=True,
230 help="If True, each line of output will be a JSON object with the "
230 help="If True, each line of output will be a JSON object with the "
231 "details from the server info file.")
231 "details from the server info file.")
232
232
233 def start(self):
233 def start(self):
234 if not self.json:
234 if not self.json:
235 print("Currently running servers:")
235 print("Currently running servers:")
236 for serverinfo in list_running_servers(self.profile):
236 for serverinfo in list_running_servers(self.profile):
237 if self.json:
237 if self.json:
238 print(json.dumps(serverinfo))
238 print(json.dumps(serverinfo))
239 else:
239 else:
240 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
240 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
241
241
242 #-----------------------------------------------------------------------------
242 #-----------------------------------------------------------------------------
243 # Aliases and Flags
243 # Aliases and Flags
244 #-----------------------------------------------------------------------------
244 #-----------------------------------------------------------------------------
245
245
246 flags = dict(kernel_flags)
246 flags = dict(kernel_flags)
247 flags['no-browser']=(
247 flags['no-browser']=(
248 {'NotebookApp' : {'open_browser' : False}},
248 {'NotebookApp' : {'open_browser' : False}},
249 "Don't open the notebook in a browser after startup."
249 "Don't open the notebook in a browser after startup."
250 )
250 )
251 flags['no-mathjax']=(
251 flags['no-mathjax']=(
252 {'NotebookApp' : {'enable_mathjax' : False}},
252 {'NotebookApp' : {'enable_mathjax' : False}},
253 """Disable MathJax
253 """Disable MathJax
254
254
255 MathJax is the javascript library IPython uses to render math/LaTeX. It is
255 MathJax is the javascript library IPython uses to render math/LaTeX. It is
256 very large, so you may want to disable it if you have a slow internet
256 very large, so you may want to disable it if you have a slow internet
257 connection, or for offline use of the notebook.
257 connection, or for offline use of the notebook.
258
258
259 When disabled, equations etc. will appear as their untransformed TeX source.
259 When disabled, equations etc. will appear as their untransformed TeX source.
260 """
260 """
261 )
261 )
262
262
263 # Add notebook manager flags
263 # Add notebook manager flags
264 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
264 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
265 'Auto-save a .py script everytime the .ipynb notebook is saved',
265 'Auto-save a .py script everytime the .ipynb notebook is saved',
266 'Do not auto-save .py scripts for every notebook'))
266 'Do not auto-save .py scripts for every notebook'))
267
267
268 # the flags that are specific to the frontend
268 # the flags that are specific to the frontend
269 # these must be scrubbed before being passed to the kernel,
269 # these must be scrubbed before being passed to the kernel,
270 # or it will raise an error on unrecognized flags
270 # or it will raise an error on unrecognized flags
271 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
271 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
272
272
273 aliases = dict(kernel_aliases)
273 aliases = dict(kernel_aliases)
274
274
275 aliases.update({
275 aliases.update({
276 'ip': 'NotebookApp.ip',
276 'ip': 'NotebookApp.ip',
277 'port': 'NotebookApp.port',
277 'port': 'NotebookApp.port',
278 'port-retries': 'NotebookApp.port_retries',
278 'port-retries': 'NotebookApp.port_retries',
279 'transport': 'KernelManager.transport',
279 'transport': 'KernelManager.transport',
280 'keyfile': 'NotebookApp.keyfile',
280 'keyfile': 'NotebookApp.keyfile',
281 'certfile': 'NotebookApp.certfile',
281 'certfile': 'NotebookApp.certfile',
282 'notebook-dir': 'NotebookManager.notebook_dir',
282 'notebook-dir': 'NotebookManager.notebook_dir',
283 'browser': 'NotebookApp.browser',
283 'browser': 'NotebookApp.browser',
284 })
284 })
285
285
286 # remove ipkernel flags that are singletons, and don't make sense in
286 # remove ipkernel flags that are singletons, and don't make sense in
287 # multi-kernel evironment:
287 # multi-kernel evironment:
288 aliases.pop('f', None)
288 aliases.pop('f', None)
289
289
290 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
290 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
291 u'notebook-dir', u'profile', u'profile-dir']
291 u'notebook-dir', u'profile', u'profile-dir']
292
292
293 #-----------------------------------------------------------------------------
293 #-----------------------------------------------------------------------------
294 # NotebookApp
294 # NotebookApp
295 #-----------------------------------------------------------------------------
295 #-----------------------------------------------------------------------------
296
296
297 class NotebookApp(BaseIPythonApplication):
297 class NotebookApp(BaseIPythonApplication):
298
298
299 name = 'ipython-notebook'
299 name = 'ipython-notebook'
300
300
301 description = """
301 description = """
302 The IPython HTML Notebook.
302 The IPython HTML Notebook.
303
303
304 This launches a Tornado based HTML Notebook Server that serves up an
304 This launches a Tornado based HTML Notebook Server that serves up an
305 HTML5/Javascript Notebook client.
305 HTML5/Javascript Notebook client.
306 """
306 """
307 examples = _examples
307 examples = _examples
308
308
309 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
309 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
310 FileNotebookManager]
310 FileNotebookManager]
311 flags = Dict(flags)
311 flags = Dict(flags)
312 aliases = Dict(aliases)
312 aliases = Dict(aliases)
313
313
314 subcommands = dict(
314 subcommands = dict(
315 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
315 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
316 )
316 )
317
317
318 kernel_argv = List(Unicode)
318 kernel_argv = List(Unicode)
319
319
320 def _log_level_default(self):
320 def _log_level_default(self):
321 return logging.INFO
321 return logging.INFO
322
322
323 def _log_format_default(self):
323 def _log_format_default(self):
324 """override default log format to include time"""
324 """override default log format to include time"""
325 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
325 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
326
326
327 # create requested profiles by default, if they don't exist:
327 # create requested profiles by default, if they don't exist:
328 auto_create = Bool(True)
328 auto_create = Bool(True)
329
329
330 # file to be opened in the notebook server
330 # file to be opened in the notebook server
331 file_to_run = Unicode('')
331 file_to_run = Unicode('')
332
332
333 # Network related information.
333 # Network related information.
334
334
335 ip = Unicode(config=True,
335 ip = Unicode(config=True,
336 help="The IP address the notebook server will listen on."
336 help="The IP address the notebook server will listen on."
337 )
337 )
338 def _ip_default(self):
338 def _ip_default(self):
339 return localhost()
339 return localhost()
340
340
341 def _ip_changed(self, name, old, new):
341 def _ip_changed(self, name, old, new):
342 if new == u'*': self.ip = u''
342 if new == u'*': self.ip = u''
343
343
344 port = Integer(8888, config=True,
344 port = Integer(8888, config=True,
345 help="The port the notebook server will listen on."
345 help="The port the notebook server will listen on."
346 )
346 )
347 port_retries = Integer(50, config=True,
347 port_retries = Integer(50, config=True,
348 help="The number of additional ports to try if the specified port is not available."
348 help="The number of additional ports to try if the specified port is not available."
349 )
349 )
350
350
351 certfile = Unicode(u'', config=True,
351 certfile = Unicode(u'', config=True,
352 help="""The full path to an SSL/TLS certificate file."""
352 help="""The full path to an SSL/TLS certificate file."""
353 )
353 )
354
354
355 keyfile = Unicode(u'', config=True,
355 keyfile = Unicode(u'', config=True,
356 help="""The full path to a private key file for usage with SSL/TLS."""
356 help="""The full path to a private key file for usage with SSL/TLS."""
357 )
357 )
358
358
359 cookie_secret = Bytes(b'', config=True,
359 cookie_secret = Bytes(b'', config=True,
360 help="""The random bytes used to secure cookies.
360 help="""The random bytes used to secure cookies.
361 By default this is a new random number every time you start the Notebook.
361 By default this is a new random number every time you start the Notebook.
362 Set it to a value in a config file to enable logins to persist across server sessions.
362 Set it to a value in a config file to enable logins to persist across server sessions.
363
363
364 Note: Cookie secrets should be kept private, do not share config files with
364 Note: Cookie secrets should be kept private, do not share config files with
365 cookie_secret stored in plaintext (you can read the value from a file).
365 cookie_secret stored in plaintext (you can read the value from a file).
366 """
366 """
367 )
367 )
368 def _cookie_secret_default(self):
368 def _cookie_secret_default(self):
369 return os.urandom(1024)
369 return os.urandom(1024)
370
370
371 password = Unicode(u'', config=True,
371 password = Unicode(u'', config=True,
372 help="""Hashed password to use for web authentication.
372 help="""Hashed password to use for web authentication.
373
373
374 To generate, type in a python/IPython shell:
374 To generate, type in a python/IPython shell:
375
375
376 from IPython.lib import passwd; passwd()
376 from IPython.lib import passwd; passwd()
377
377
378 The string should be of the form type:salt:hashed-password.
378 The string should be of the form type:salt:hashed-password.
379 """
379 """
380 )
380 )
381
381
382 open_browser = Bool(True, config=True,
382 open_browser = Bool(True, config=True,
383 help="""Whether to open in a browser after starting.
383 help="""Whether to open in a browser after starting.
384 The specific browser used is platform dependent and
384 The specific browser used is platform dependent and
385 determined by the python standard library `webbrowser`
385 determined by the python standard library `webbrowser`
386 module, unless it is overridden using the --browser
386 module, unless it is overridden using the --browser
387 (NotebookApp.browser) configuration option.
387 (NotebookApp.browser) configuration option.
388 """)
388 """)
389
389
390 browser = Unicode(u'', config=True,
390 browser = Unicode(u'', config=True,
391 help="""Specify what command to use to invoke a web
391 help="""Specify what command to use to invoke a web
392 browser when opening the notebook. If not specified, the
392 browser when opening the notebook. If not specified, the
393 default browser will be determined by the `webbrowser`
393 default browser will be determined by the `webbrowser`
394 standard library module, which allows setting of the
394 standard library module, which allows setting of the
395 BROWSER environment variable to override it.
395 BROWSER environment variable to override it.
396 """)
396 """)
397
397
398 webapp_settings = Dict(config=True,
398 webapp_settings = Dict(config=True,
399 help="Supply overrides for the tornado.web.Application that the "
399 help="Supply overrides for the tornado.web.Application that the "
400 "IPython notebook uses.")
400 "IPython notebook uses.")
401
401
402 enable_mathjax = Bool(True, config=True,
402 enable_mathjax = Bool(True, config=True,
403 help="""Whether to enable MathJax for typesetting math/TeX
403 help="""Whether to enable MathJax for typesetting math/TeX
404
404
405 MathJax is the javascript library IPython uses to render math/LaTeX. It is
405 MathJax is the javascript library IPython uses to render math/LaTeX. It is
406 very large, so you may want to disable it if you have a slow internet
406 very large, so you may want to disable it if you have a slow internet
407 connection, or for offline use of the notebook.
407 connection, or for offline use of the notebook.
408
408
409 When disabled, equations etc. will appear as their untransformed TeX source.
409 When disabled, equations etc. will appear as their untransformed TeX source.
410 """
410 """
411 )
411 )
412 def _enable_mathjax_changed(self, name, old, new):
412 def _enable_mathjax_changed(self, name, old, new):
413 """set mathjax url to empty if mathjax is disabled"""
413 """set mathjax url to empty if mathjax is disabled"""
414 if not new:
414 if not new:
415 self.mathjax_url = u''
415 self.mathjax_url = u''
416
416
417 base_project_url = Unicode('/', config=True,
417 base_url = Unicode('/', config=True,
418 help='''The base URL for the notebook server.
418 help='''The base URL for the notebook server.
419
419
420 Leading and trailing slashes can be omitted,
420 Leading and trailing slashes can be omitted,
421 and will automatically be added.
421 and will automatically be added.
422 ''')
422 ''')
423 def _base_project_url_changed(self, name, old, new):
423 def _base_url_changed(self, name, old, new):
424 if not new.startswith('/'):
424 if not new.startswith('/'):
425 self.base_project_url = '/'+new
425 self.base_url = '/'+new
426 elif not new.endswith('/'):
426 elif not new.endswith('/'):
427 self.base_project_url = new+'/'
427 self.base_url = new+'/'
428
429 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
430 def _base_project_url_changed(self, name, old, new):
431 self.log.warn("base_project_url is deprecated, use base_url")
432 self.base_url = new
428
433
429 base_kernel_url = Unicode('/', config=True,
434 base_kernel_url = Unicode('/', config=True,
430 help='''The base URL for the kernel server
435 help='''The base URL for the kernel server
431
436
432 Leading and trailing slashes can be omitted,
437 Leading and trailing slashes can be omitted,
433 and will automatically be added.
438 and will automatically be added.
434 ''')
439 ''')
435 def _base_kernel_url_changed(self, name, old, new):
440 def _base_kernel_url_changed(self, name, old, new):
436 if not new.startswith('/'):
441 if not new.startswith('/'):
437 self.base_kernel_url = '/'+new
442 self.base_kernel_url = '/'+new
438 elif not new.endswith('/'):
443 elif not new.endswith('/'):
439 self.base_kernel_url = new+'/'
444 self.base_kernel_url = new+'/'
440
445
441 websocket_url = Unicode("", config=True,
446 websocket_url = Unicode("", config=True,
442 help="""The base URL for the websocket server,
447 help="""The base URL for the websocket server,
443 if it differs from the HTTP server (hint: it almost certainly doesn't).
448 if it differs from the HTTP server (hint: it almost certainly doesn't).
444
449
445 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
450 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
446 """
451 """
447 )
452 )
448
453
449 extra_static_paths = List(Unicode, config=True,
454 extra_static_paths = List(Unicode, config=True,
450 help="""Extra paths to search for serving static files.
455 help="""Extra paths to search for serving static files.
451
456
452 This allows adding javascript/css to be available from the notebook server machine,
457 This allows adding javascript/css to be available from the notebook server machine,
453 or overriding individual files in the IPython"""
458 or overriding individual files in the IPython"""
454 )
459 )
455 def _extra_static_paths_default(self):
460 def _extra_static_paths_default(self):
456 return [os.path.join(self.profile_dir.location, 'static')]
461 return [os.path.join(self.profile_dir.location, 'static')]
457
462
458 @property
463 @property
459 def static_file_path(self):
464 def static_file_path(self):
460 """return extra paths + the default location"""
465 """return extra paths + the default location"""
461 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
466 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
462
467
463 nbextensions_path = List(Unicode, config=True,
468 nbextensions_path = List(Unicode, config=True,
464 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
469 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
465 )
470 )
466 def _nbextensions_path_default(self):
471 def _nbextensions_path_default(self):
467 return [os.path.join(get_ipython_dir(), 'nbextensions')]
472 return [os.path.join(get_ipython_dir(), 'nbextensions')]
468
473
469 mathjax_url = Unicode("", config=True,
474 mathjax_url = Unicode("", config=True,
470 help="""The url for MathJax.js."""
475 help="""The url for MathJax.js."""
471 )
476 )
472 def _mathjax_url_default(self):
477 def _mathjax_url_default(self):
473 if not self.enable_mathjax:
478 if not self.enable_mathjax:
474 return u''
479 return u''
475 static_url_prefix = self.webapp_settings.get("static_url_prefix",
480 static_url_prefix = self.webapp_settings.get("static_url_prefix",
476 url_path_join(self.base_project_url, "static")
481 url_path_join(self.base_url, "static")
477 )
482 )
478
483
479 # try local mathjax, either in nbextensions/mathjax or static/mathjax
484 # try local mathjax, either in nbextensions/mathjax or static/mathjax
480 for (url_prefix, search_path) in [
485 for (url_prefix, search_path) in [
481 (url_path_join(self.base_project_url, "nbextensions"), self.nbextensions_path),
486 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
482 (static_url_prefix, self.static_file_path),
487 (static_url_prefix, self.static_file_path),
483 ]:
488 ]:
484 self.log.debug("searching for local mathjax in %s", search_path)
489 self.log.debug("searching for local mathjax in %s", search_path)
485 try:
490 try:
486 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
491 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
487 except IOError:
492 except IOError:
488 continue
493 continue
489 else:
494 else:
490 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
495 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
491 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
496 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
492 return url
497 return url
493
498
494 # no local mathjax, serve from CDN
499 # no local mathjax, serve from CDN
495 if self.certfile:
500 if self.certfile:
496 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
501 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
497 host = u"https://c328740.ssl.cf1.rackcdn.com"
502 host = u"https://c328740.ssl.cf1.rackcdn.com"
498 else:
503 else:
499 host = u"http://cdn.mathjax.org"
504 host = u"http://cdn.mathjax.org"
500
505
501 url = host + u"/mathjax/latest/MathJax.js"
506 url = host + u"/mathjax/latest/MathJax.js"
502 self.log.info("Using MathJax from CDN: %s", url)
507 self.log.info("Using MathJax from CDN: %s", url)
503 return url
508 return url
504
509
505 def _mathjax_url_changed(self, name, old, new):
510 def _mathjax_url_changed(self, name, old, new):
506 if new and not self.enable_mathjax:
511 if new and not self.enable_mathjax:
507 # enable_mathjax=False overrides mathjax_url
512 # enable_mathjax=False overrides mathjax_url
508 self.mathjax_url = u''
513 self.mathjax_url = u''
509 else:
514 else:
510 self.log.info("Using MathJax: %s", new)
515 self.log.info("Using MathJax: %s", new)
511
516
512 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
517 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
513 config=True,
518 config=True,
514 help='The notebook manager class to use.')
519 help='The notebook manager class to use.')
515
520
516 trust_xheaders = Bool(False, config=True,
521 trust_xheaders = Bool(False, config=True,
517 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
522 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
518 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
523 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
519 )
524 )
520
525
521 info_file = Unicode()
526 info_file = Unicode()
522
527
523 def _info_file_default(self):
528 def _info_file_default(self):
524 info_file = "nbserver-%s.json"%os.getpid()
529 info_file = "nbserver-%s.json"%os.getpid()
525 return os.path.join(self.profile_dir.security_dir, info_file)
530 return os.path.join(self.profile_dir.security_dir, info_file)
526
531
527 def parse_command_line(self, argv=None):
532 def parse_command_line(self, argv=None):
528 super(NotebookApp, self).parse_command_line(argv)
533 super(NotebookApp, self).parse_command_line(argv)
529
534
530 if self.extra_args:
535 if self.extra_args:
531 arg0 = self.extra_args[0]
536 arg0 = self.extra_args[0]
532 f = os.path.abspath(arg0)
537 f = os.path.abspath(arg0)
533 self.argv.remove(arg0)
538 self.argv.remove(arg0)
534 if not os.path.exists(f):
539 if not os.path.exists(f):
535 self.log.critical("No such file or directory: %s", f)
540 self.log.critical("No such file or directory: %s", f)
536 self.exit(1)
541 self.exit(1)
537 if os.path.isdir(f):
542 if os.path.isdir(f):
538 self.config.FileNotebookManager.notebook_dir = f
543 self.config.FileNotebookManager.notebook_dir = f
539 elif os.path.isfile(f):
544 elif os.path.isfile(f):
540 self.file_to_run = f
545 self.file_to_run = f
541
546
542 def init_kernel_argv(self):
547 def init_kernel_argv(self):
543 """construct the kernel arguments"""
548 """construct the kernel arguments"""
544 # Scrub frontend-specific flags
549 # Scrub frontend-specific flags
545 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
550 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
546 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
551 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
547 self.log.warn('\n '.join([
552 self.log.warn('\n '.join([
548 "Starting all kernels in pylab mode is not recommended,",
553 "Starting all kernels in pylab mode is not recommended,",
549 "and will be disabled in a future release.",
554 "and will be disabled in a future release.",
550 "Please use the %matplotlib magic to enable matplotlib instead.",
555 "Please use the %matplotlib magic to enable matplotlib instead.",
551 "pylab implies many imports, which can have confusing side effects",
556 "pylab implies many imports, which can have confusing side effects",
552 "and harm the reproducibility of your notebooks.",
557 "and harm the reproducibility of your notebooks.",
553 ]))
558 ]))
554 # Kernel should inherit default config file from frontend
559 # Kernel should inherit default config file from frontend
555 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
560 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
556 # Kernel should get *absolute* path to profile directory
561 # Kernel should get *absolute* path to profile directory
557 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
562 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
558
563
559 def init_configurables(self):
564 def init_configurables(self):
560 # force Session default to be secure
565 # force Session default to be secure
561 default_secure(self.config)
566 default_secure(self.config)
562 self.kernel_manager = MappingKernelManager(
567 self.kernel_manager = MappingKernelManager(
563 parent=self, log=self.log, kernel_argv=self.kernel_argv,
568 parent=self, log=self.log, kernel_argv=self.kernel_argv,
564 connection_dir = self.profile_dir.security_dir,
569 connection_dir = self.profile_dir.security_dir,
565 )
570 )
566 kls = import_item(self.notebook_manager_class)
571 kls = import_item(self.notebook_manager_class)
567 self.notebook_manager = kls(parent=self, log=self.log)
572 self.notebook_manager = kls(parent=self, log=self.log)
568 self.session_manager = SessionManager(parent=self, log=self.log)
573 self.session_manager = SessionManager(parent=self, log=self.log)
569 self.cluster_manager = ClusterManager(parent=self, log=self.log)
574 self.cluster_manager = ClusterManager(parent=self, log=self.log)
570 self.cluster_manager.update_profiles()
575 self.cluster_manager.update_profiles()
571
576
572 def init_logging(self):
577 def init_logging(self):
573 # This prevents double log messages because tornado use a root logger that
578 # This prevents double log messages because tornado use a root logger that
574 # self.log is a child of. The logging module dipatches log messages to a log
579 # self.log is a child of. The logging module dipatches log messages to a log
575 # and all of its ancenstors until propagate is set to False.
580 # and all of its ancenstors until propagate is set to False.
576 self.log.propagate = False
581 self.log.propagate = False
577
582
578 # hook up tornado 3's loggers to our app handlers
583 # hook up tornado 3's loggers to our app handlers
579 for name in ('access', 'application', 'general'):
584 for name in ('access', 'application', 'general'):
580 logger = logging.getLogger('tornado.%s' % name)
585 logger = logging.getLogger('tornado.%s' % name)
581 logger.parent = self.log
586 logger.parent = self.log
582 logger.setLevel(self.log.level)
587 logger.setLevel(self.log.level)
583
588
584 def init_webapp(self):
589 def init_webapp(self):
585 """initialize tornado webapp and httpserver"""
590 """initialize tornado webapp and httpserver"""
586 self.web_app = NotebookWebApplication(
591 self.web_app = NotebookWebApplication(
587 self, self.kernel_manager, self.notebook_manager,
592 self, self.kernel_manager, self.notebook_manager,
588 self.cluster_manager, self.session_manager,
593 self.cluster_manager, self.session_manager,
589 self.log, self.base_project_url, self.webapp_settings
594 self.log, self.base_url, self.webapp_settings
590 )
595 )
591 if self.certfile:
596 if self.certfile:
592 ssl_options = dict(certfile=self.certfile)
597 ssl_options = dict(certfile=self.certfile)
593 if self.keyfile:
598 if self.keyfile:
594 ssl_options['keyfile'] = self.keyfile
599 ssl_options['keyfile'] = self.keyfile
595 else:
600 else:
596 ssl_options = None
601 ssl_options = None
597 self.web_app.password = self.password
602 self.web_app.password = self.password
598 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
603 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
599 xheaders=self.trust_xheaders)
604 xheaders=self.trust_xheaders)
600 if not self.ip:
605 if not self.ip:
601 warning = "WARNING: The notebook server is listening on all IP addresses"
606 warning = "WARNING: The notebook server is listening on all IP addresses"
602 if ssl_options is None:
607 if ssl_options is None:
603 self.log.critical(warning + " and not using encryption. This "
608 self.log.critical(warning + " and not using encryption. This "
604 "is not recommended.")
609 "is not recommended.")
605 if not self.password:
610 if not self.password:
606 self.log.critical(warning + " and not using authentication. "
611 self.log.critical(warning + " and not using authentication. "
607 "This is highly insecure and not recommended.")
612 "This is highly insecure and not recommended.")
608 success = None
613 success = None
609 for port in random_ports(self.port, self.port_retries+1):
614 for port in random_ports(self.port, self.port_retries+1):
610 try:
615 try:
611 self.http_server.listen(port, self.ip)
616 self.http_server.listen(port, self.ip)
612 except socket.error as e:
617 except socket.error as e:
613 if e.errno == errno.EADDRINUSE:
618 if e.errno == errno.EADDRINUSE:
614 self.log.info('The port %i is already in use, trying another random port.' % port)
619 self.log.info('The port %i is already in use, trying another random port.' % port)
615 continue
620 continue
616 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
621 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
617 self.log.warn("Permission to listen on port %i denied" % port)
622 self.log.warn("Permission to listen on port %i denied" % port)
618 continue
623 continue
619 else:
624 else:
620 raise
625 raise
621 else:
626 else:
622 self.port = port
627 self.port = port
623 success = True
628 success = True
624 break
629 break
625 if not success:
630 if not success:
626 self.log.critical('ERROR: the notebook server could not be started because '
631 self.log.critical('ERROR: the notebook server could not be started because '
627 'no available port could be found.')
632 'no available port could be found.')
628 self.exit(1)
633 self.exit(1)
629
634
630 @property
635 @property
631 def display_url(self):
636 def display_url(self):
632 ip = self.ip if self.ip else '[all ip addresses on your system]'
637 ip = self.ip if self.ip else '[all ip addresses on your system]'
633 return self._url(ip)
638 return self._url(ip)
634
639
635 @property
640 @property
636 def connection_url(self):
641 def connection_url(self):
637 ip = self.ip if self.ip else localhost()
642 ip = self.ip if self.ip else localhost()
638 return self._url(ip)
643 return self._url(ip)
639
644
640 def _url(self, ip):
645 def _url(self, ip):
641 proto = 'https' if self.certfile else 'http'
646 proto = 'https' if self.certfile else 'http'
642 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_project_url)
647 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
643
648
644 def init_signal(self):
649 def init_signal(self):
645 if not sys.platform.startswith('win'):
650 if not sys.platform.startswith('win'):
646 signal.signal(signal.SIGINT, self._handle_sigint)
651 signal.signal(signal.SIGINT, self._handle_sigint)
647 signal.signal(signal.SIGTERM, self._signal_stop)
652 signal.signal(signal.SIGTERM, self._signal_stop)
648 if hasattr(signal, 'SIGUSR1'):
653 if hasattr(signal, 'SIGUSR1'):
649 # Windows doesn't support SIGUSR1
654 # Windows doesn't support SIGUSR1
650 signal.signal(signal.SIGUSR1, self._signal_info)
655 signal.signal(signal.SIGUSR1, self._signal_info)
651 if hasattr(signal, 'SIGINFO'):
656 if hasattr(signal, 'SIGINFO'):
652 # only on BSD-based systems
657 # only on BSD-based systems
653 signal.signal(signal.SIGINFO, self._signal_info)
658 signal.signal(signal.SIGINFO, self._signal_info)
654
659
655 def _handle_sigint(self, sig, frame):
660 def _handle_sigint(self, sig, frame):
656 """SIGINT handler spawns confirmation dialog"""
661 """SIGINT handler spawns confirmation dialog"""
657 # register more forceful signal handler for ^C^C case
662 # register more forceful signal handler for ^C^C case
658 signal.signal(signal.SIGINT, self._signal_stop)
663 signal.signal(signal.SIGINT, self._signal_stop)
659 # request confirmation dialog in bg thread, to avoid
664 # request confirmation dialog in bg thread, to avoid
660 # blocking the App
665 # blocking the App
661 thread = threading.Thread(target=self._confirm_exit)
666 thread = threading.Thread(target=self._confirm_exit)
662 thread.daemon = True
667 thread.daemon = True
663 thread.start()
668 thread.start()
664
669
665 def _restore_sigint_handler(self):
670 def _restore_sigint_handler(self):
666 """callback for restoring original SIGINT handler"""
671 """callback for restoring original SIGINT handler"""
667 signal.signal(signal.SIGINT, self._handle_sigint)
672 signal.signal(signal.SIGINT, self._handle_sigint)
668
673
669 def _confirm_exit(self):
674 def _confirm_exit(self):
670 """confirm shutdown on ^C
675 """confirm shutdown on ^C
671
676
672 A second ^C, or answering 'y' within 5s will cause shutdown,
677 A second ^C, or answering 'y' within 5s will cause shutdown,
673 otherwise original SIGINT handler will be restored.
678 otherwise original SIGINT handler will be restored.
674
679
675 This doesn't work on Windows.
680 This doesn't work on Windows.
676 """
681 """
677 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
682 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
678 time.sleep(0.1)
683 time.sleep(0.1)
679 info = self.log.info
684 info = self.log.info
680 info('interrupted')
685 info('interrupted')
681 print(self.notebook_info())
686 print(self.notebook_info())
682 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
687 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
683 sys.stdout.flush()
688 sys.stdout.flush()
684 r,w,x = select.select([sys.stdin], [], [], 5)
689 r,w,x = select.select([sys.stdin], [], [], 5)
685 if r:
690 if r:
686 line = sys.stdin.readline()
691 line = sys.stdin.readline()
687 if line.lower().startswith('y'):
692 if line.lower().startswith('y'):
688 self.log.critical("Shutdown confirmed")
693 self.log.critical("Shutdown confirmed")
689 ioloop.IOLoop.instance().stop()
694 ioloop.IOLoop.instance().stop()
690 return
695 return
691 else:
696 else:
692 print("No answer for 5s:", end=' ')
697 print("No answer for 5s:", end=' ')
693 print("resuming operation...")
698 print("resuming operation...")
694 # no answer, or answer is no:
699 # no answer, or answer is no:
695 # set it back to original SIGINT handler
700 # set it back to original SIGINT handler
696 # use IOLoop.add_callback because signal.signal must be called
701 # use IOLoop.add_callback because signal.signal must be called
697 # from main thread
702 # from main thread
698 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
703 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
699
704
700 def _signal_stop(self, sig, frame):
705 def _signal_stop(self, sig, frame):
701 self.log.critical("received signal %s, stopping", sig)
706 self.log.critical("received signal %s, stopping", sig)
702 ioloop.IOLoop.instance().stop()
707 ioloop.IOLoop.instance().stop()
703
708
704 def _signal_info(self, sig, frame):
709 def _signal_info(self, sig, frame):
705 print(self.notebook_info())
710 print(self.notebook_info())
706
711
707 def init_components(self):
712 def init_components(self):
708 """Check the components submodule, and warn if it's unclean"""
713 """Check the components submodule, and warn if it's unclean"""
709 status = submodule.check_submodule_status()
714 status = submodule.check_submodule_status()
710 if status == 'missing':
715 if status == 'missing':
711 self.log.warn("components submodule missing, running `git submodule update`")
716 self.log.warn("components submodule missing, running `git submodule update`")
712 submodule.update_submodules(submodule.ipython_parent())
717 submodule.update_submodules(submodule.ipython_parent())
713 elif status == 'unclean':
718 elif status == 'unclean':
714 self.log.warn("components submodule unclean, you may see 404s on static/components")
719 self.log.warn("components submodule unclean, you may see 404s on static/components")
715 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
720 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
716
721
717 @catch_config_error
722 @catch_config_error
718 def initialize(self, argv=None):
723 def initialize(self, argv=None):
719 super(NotebookApp, self).initialize(argv)
724 super(NotebookApp, self).initialize(argv)
720 self.init_logging()
725 self.init_logging()
721 self.init_kernel_argv()
726 self.init_kernel_argv()
722 self.init_configurables()
727 self.init_configurables()
723 self.init_components()
728 self.init_components()
724 self.init_webapp()
729 self.init_webapp()
725 self.init_signal()
730 self.init_signal()
726
731
727 def cleanup_kernels(self):
732 def cleanup_kernels(self):
728 """Shutdown all kernels.
733 """Shutdown all kernels.
729
734
730 The kernels will shutdown themselves when this process no longer exists,
735 The kernels will shutdown themselves when this process no longer exists,
731 but explicit shutdown allows the KernelManagers to cleanup the connection files.
736 but explicit shutdown allows the KernelManagers to cleanup the connection files.
732 """
737 """
733 self.log.info('Shutting down kernels')
738 self.log.info('Shutting down kernels')
734 self.kernel_manager.shutdown_all()
739 self.kernel_manager.shutdown_all()
735
740
736 def notebook_info(self):
741 def notebook_info(self):
737 "Return the current working directory and the server url information"
742 "Return the current working directory and the server url information"
738 info = self.notebook_manager.info_string() + "\n"
743 info = self.notebook_manager.info_string() + "\n"
739 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
744 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
740 return info + "The IPython Notebook is running at: %s" % self.display_url
745 return info + "The IPython Notebook is running at: %s" % self.display_url
741
746
742 def server_info(self):
747 def server_info(self):
743 """Return a JSONable dict of information about this server."""
748 """Return a JSONable dict of information about this server."""
744 return {'url': self.connection_url,
749 return {'url': self.connection_url,
745 'hostname': self.ip if self.ip else 'localhost',
750 'hostname': self.ip if self.ip else 'localhost',
746 'port': self.port,
751 'port': self.port,
747 'secure': bool(self.certfile),
752 'secure': bool(self.certfile),
748 'base_project_url': self.base_project_url,
753 'base_url': self.base_url,
749 'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
754 'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
750 }
755 }
751
756
752 def write_server_info_file(self):
757 def write_server_info_file(self):
753 """Write the result of server_info() to the JSON file info_file."""
758 """Write the result of server_info() to the JSON file info_file."""
754 with open(self.info_file, 'w') as f:
759 with open(self.info_file, 'w') as f:
755 json.dump(self.server_info(), f, indent=2)
760 json.dump(self.server_info(), f, indent=2)
756
761
757 def remove_server_info_file(self):
762 def remove_server_info_file(self):
758 """Remove the nbserver-<pid>.json file created for this server.
763 """Remove the nbserver-<pid>.json file created for this server.
759
764
760 Ignores the error raised when the file has already been removed.
765 Ignores the error raised when the file has already been removed.
761 """
766 """
762 try:
767 try:
763 os.unlink(self.info_file)
768 os.unlink(self.info_file)
764 except OSError as e:
769 except OSError as e:
765 if e.errno != errno.ENOENT:
770 if e.errno != errno.ENOENT:
766 raise
771 raise
767
772
768 def start(self):
773 def start(self):
769 """ Start the IPython Notebook server app, after initialization
774 """ Start the IPython Notebook server app, after initialization
770
775
771 This method takes no arguments so all configuration and initialization
776 This method takes no arguments so all configuration and initialization
772 must be done prior to calling this method."""
777 must be done prior to calling this method."""
773 if self.subapp is not None:
778 if self.subapp is not None:
774 return self.subapp.start()
779 return self.subapp.start()
775
780
776 info = self.log.info
781 info = self.log.info
777 for line in self.notebook_info().split("\n"):
782 for line in self.notebook_info().split("\n"):
778 info(line)
783 info(line)
779 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
784 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
780
785
781 self.write_server_info_file()
786 self.write_server_info_file()
782
787
783 if self.open_browser or self.file_to_run:
788 if self.open_browser or self.file_to_run:
784 try:
789 try:
785 browser = webbrowser.get(self.browser or None)
790 browser = webbrowser.get(self.browser or None)
786 except webbrowser.Error as e:
791 except webbrowser.Error as e:
787 self.log.warn('No web browser found: %s.' % e)
792 self.log.warn('No web browser found: %s.' % e)
788 browser = None
793 browser = None
789
794
790 f = self.file_to_run
795 f = self.file_to_run
791 if f:
796 if f:
792 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
797 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
793 if f.startswith(nbdir):
798 if f.startswith(nbdir):
794 f = f[len(nbdir):]
799 f = f[len(nbdir):]
795 else:
800 else:
796 self.log.warn(
801 self.log.warn(
797 "Probably won't be able to open notebook %s "
802 "Probably won't be able to open notebook %s "
798 "because it is not in notebook_dir %s",
803 "because it is not in notebook_dir %s",
799 f, nbdir,
804 f, nbdir,
800 )
805 )
801
806
802 if os.path.isfile(self.file_to_run):
807 if os.path.isfile(self.file_to_run):
803 url = url_path_join('notebooks', f)
808 url = url_path_join('notebooks', f)
804 else:
809 else:
805 url = url_path_join('tree', f)
810 url = url_path_join('tree', f)
806 if browser:
811 if browser:
807 b = lambda : browser.open("%s%s" % (self.connection_url, url),
812 b = lambda : browser.open("%s%s" % (self.connection_url, url),
808 new=2)
813 new=2)
809 threading.Thread(target=b).start()
814 threading.Thread(target=b).start()
810 try:
815 try:
811 ioloop.IOLoop.instance().start()
816 ioloop.IOLoop.instance().start()
812 except KeyboardInterrupt:
817 except KeyboardInterrupt:
813 info("Interrupted...")
818 info("Interrupted...")
814 finally:
819 finally:
815 self.cleanup_kernels()
820 self.cleanup_kernels()
816 self.remove_server_info_file()
821 self.remove_server_info_file()
817
822
818
823
819 def list_running_servers(profile='default'):
824 def list_running_servers(profile='default'):
820 """Iterate over the server info files of running notebook servers.
825 """Iterate over the server info files of running notebook servers.
821
826
822 Given a profile name, find nbserver-* files in the security directory of
827 Given a profile name, find nbserver-* files in the security directory of
823 that profile, and yield dicts of their information, each one pertaining to
828 that profile, and yield dicts of their information, each one pertaining to
824 a currently running notebook server instance.
829 a currently running notebook server instance.
825 """
830 """
826 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
831 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
827 for file in os.listdir(pd.security_dir):
832 for file in os.listdir(pd.security_dir):
828 if file.startswith('nbserver-'):
833 if file.startswith('nbserver-'):
829 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
834 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
830 yield json.load(f)
835 yield json.load(f)
831
836
832 #-----------------------------------------------------------------------------
837 #-----------------------------------------------------------------------------
833 # Main entry point
838 # Main entry point
834 #-----------------------------------------------------------------------------
839 #-----------------------------------------------------------------------------
835
840
836 launch_new_instance = NotebookApp.launch_instance
841 launch_new_instance = NotebookApp.launch_instance
837
842
@@ -1,290 +1,290 b''
1 """Tornado handlers for the notebooks web service.
1 """Tornado handlers for the notebooks web service.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import json
19 import json
20
20
21 from tornado import web
21 from tornado import web
22
22
23 from IPython.html.utils import url_path_join, url_escape
23 from IPython.html.utils import url_path_join, url_escape
24 from IPython.utils.jsonutil import date_default
24 from IPython.utils.jsonutil import date_default
25
25
26 from IPython.html.base.handlers import (IPythonHandler, json_errors,
26 from IPython.html.base.handlers import (IPythonHandler, json_errors,
27 notebook_path_regex, path_regex,
27 notebook_path_regex, path_regex,
28 notebook_name_regex)
28 notebook_name_regex)
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Notebook web service handlers
31 # Notebook web service handlers
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34
34
35 class NotebookHandler(IPythonHandler):
35 class NotebookHandler(IPythonHandler):
36
36
37 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
37 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
38
38
39 def notebook_location(self, name, path=''):
39 def notebook_location(self, name, path=''):
40 """Return the full URL location of a notebook based.
40 """Return the full URL location of a notebook based.
41
41
42 Parameters
42 Parameters
43 ----------
43 ----------
44 name : unicode
44 name : unicode
45 The base name of the notebook, such as "foo.ipynb".
45 The base name of the notebook, such as "foo.ipynb".
46 path : unicode
46 path : unicode
47 The URL path of the notebook.
47 The URL path of the notebook.
48 """
48 """
49 return url_escape(url_path_join(
49 return url_escape(url_path_join(
50 self.base_project_url, 'api', 'notebooks', path, name
50 self.base_url, 'api', 'notebooks', path, name
51 ))
51 ))
52
52
53 def _finish_model(self, model, location=True):
53 def _finish_model(self, model, location=True):
54 """Finish a JSON request with a model, setting relevant headers, etc."""
54 """Finish a JSON request with a model, setting relevant headers, etc."""
55 if location:
55 if location:
56 location = self.notebook_location(model['name'], model['path'])
56 location = self.notebook_location(model['name'], model['path'])
57 self.set_header('Location', location)
57 self.set_header('Location', location)
58 self.set_header('Last-Modified', model['last_modified'])
58 self.set_header('Last-Modified', model['last_modified'])
59 self.finish(json.dumps(model, default=date_default))
59 self.finish(json.dumps(model, default=date_default))
60
60
61 @web.authenticated
61 @web.authenticated
62 @json_errors
62 @json_errors
63 def get(self, path='', name=None):
63 def get(self, path='', name=None):
64 """Return a Notebook or list of notebooks.
64 """Return a Notebook or list of notebooks.
65
65
66 * GET with path and no notebook name lists notebooks in a directory
66 * GET with path and no notebook name lists notebooks in a directory
67 * GET with path and notebook name returns notebook JSON
67 * GET with path and notebook name returns notebook JSON
68 """
68 """
69 nbm = self.notebook_manager
69 nbm = self.notebook_manager
70 # Check to see if a notebook name was given
70 # Check to see if a notebook name was given
71 if name is None:
71 if name is None:
72 # TODO: Remove this after we create the contents web service and directories are
72 # TODO: Remove this after we create the contents web service and directories are
73 # no longer listed by the notebook web service. This should only handle notebooks
73 # no longer listed by the notebook web service. This should only handle notebooks
74 # and not directories.
74 # and not directories.
75 dirs = nbm.list_dirs(path)
75 dirs = nbm.list_dirs(path)
76 notebooks = []
76 notebooks = []
77 index = []
77 index = []
78 for nb in nbm.list_notebooks(path):
78 for nb in nbm.list_notebooks(path):
79 if nb['name'].lower() == 'index.ipynb':
79 if nb['name'].lower() == 'index.ipynb':
80 index.append(nb)
80 index.append(nb)
81 else:
81 else:
82 notebooks.append(nb)
82 notebooks.append(nb)
83 notebooks = index + dirs + notebooks
83 notebooks = index + dirs + notebooks
84 self.finish(json.dumps(notebooks, default=date_default))
84 self.finish(json.dumps(notebooks, default=date_default))
85 return
85 return
86 # get and return notebook representation
86 # get and return notebook representation
87 model = nbm.get_notebook_model(name, path)
87 model = nbm.get_notebook_model(name, path)
88 self._finish_model(model, location=False)
88 self._finish_model(model, location=False)
89
89
90 @web.authenticated
90 @web.authenticated
91 @json_errors
91 @json_errors
92 def patch(self, path='', name=None):
92 def patch(self, path='', name=None):
93 """PATCH renames a notebook without re-uploading content."""
93 """PATCH renames a notebook without re-uploading content."""
94 nbm = self.notebook_manager
94 nbm = self.notebook_manager
95 if name is None:
95 if name is None:
96 raise web.HTTPError(400, u'Notebook name missing')
96 raise web.HTTPError(400, u'Notebook name missing')
97 model = self.get_json_body()
97 model = self.get_json_body()
98 if model is None:
98 if model is None:
99 raise web.HTTPError(400, u'JSON body missing')
99 raise web.HTTPError(400, u'JSON body missing')
100 model = nbm.update_notebook_model(model, name, path)
100 model = nbm.update_notebook_model(model, name, path)
101 self._finish_model(model)
101 self._finish_model(model)
102
102
103 def _copy_notebook(self, copy_from, path, copy_to=None):
103 def _copy_notebook(self, copy_from, path, copy_to=None):
104 """Copy a notebook in path, optionally specifying the new name.
104 """Copy a notebook in path, optionally specifying the new name.
105
105
106 Only support copying within the same directory.
106 Only support copying within the same directory.
107 """
107 """
108 self.log.info(u"Copying notebook from %s/%s to %s/%s",
108 self.log.info(u"Copying notebook from %s/%s to %s/%s",
109 path, copy_from,
109 path, copy_from,
110 path, copy_to or '',
110 path, copy_to or '',
111 )
111 )
112 model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
112 model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
113 self.set_status(201)
113 self.set_status(201)
114 self._finish_model(model)
114 self._finish_model(model)
115
115
116 def _upload_notebook(self, model, path, name=None):
116 def _upload_notebook(self, model, path, name=None):
117 """Upload a notebook
117 """Upload a notebook
118
118
119 If name specified, create it in path/name.
119 If name specified, create it in path/name.
120 """
120 """
121 self.log.info(u"Uploading notebook to %s/%s", path, name or '')
121 self.log.info(u"Uploading notebook to %s/%s", path, name or '')
122 if name:
122 if name:
123 model['name'] = name
123 model['name'] = name
124
124
125 model = self.notebook_manager.create_notebook_model(model, path)
125 model = self.notebook_manager.create_notebook_model(model, path)
126 self.set_status(201)
126 self.set_status(201)
127 self._finish_model(model)
127 self._finish_model(model)
128
128
129 def _create_empty_notebook(self, path, name=None):
129 def _create_empty_notebook(self, path, name=None):
130 """Create an empty notebook in path
130 """Create an empty notebook in path
131
131
132 If name specified, create it in path/name.
132 If name specified, create it in path/name.
133 """
133 """
134 self.log.info(u"Creating new notebook in %s/%s", path, name or '')
134 self.log.info(u"Creating new notebook in %s/%s", path, name or '')
135 model = {}
135 model = {}
136 if name:
136 if name:
137 model['name'] = name
137 model['name'] = name
138 model = self.notebook_manager.create_notebook_model(model, path=path)
138 model = self.notebook_manager.create_notebook_model(model, path=path)
139 self.set_status(201)
139 self.set_status(201)
140 self._finish_model(model)
140 self._finish_model(model)
141
141
142 def _save_notebook(self, model, path, name):
142 def _save_notebook(self, model, path, name):
143 """Save an existing notebook."""
143 """Save an existing notebook."""
144 self.log.info(u"Saving notebook at %s/%s", path, name)
144 self.log.info(u"Saving notebook at %s/%s", path, name)
145 model = self.notebook_manager.save_notebook_model(model, name, path)
145 model = self.notebook_manager.save_notebook_model(model, name, path)
146 if model['path'] != path.strip('/') or model['name'] != name:
146 if model['path'] != path.strip('/') or model['name'] != name:
147 # a rename happened, set Location header
147 # a rename happened, set Location header
148 location = True
148 location = True
149 else:
149 else:
150 location = False
150 location = False
151 self._finish_model(model, location)
151 self._finish_model(model, location)
152
152
153 @web.authenticated
153 @web.authenticated
154 @json_errors
154 @json_errors
155 def post(self, path='', name=None):
155 def post(self, path='', name=None):
156 """Create a new notebook in the specified path.
156 """Create a new notebook in the specified path.
157
157
158 POST creates new notebooks. The server always decides on the notebook name.
158 POST creates new notebooks. The server always decides on the notebook name.
159
159
160 POST /api/notebooks/path
160 POST /api/notebooks/path
161 New untitled notebook in path. If content specified, upload a
161 New untitled notebook in path. If content specified, upload a
162 notebook, otherwise start empty.
162 notebook, otherwise start empty.
163 POST /api/notebooks/path?copy=OtherNotebook.ipynb
163 POST /api/notebooks/path?copy=OtherNotebook.ipynb
164 New copy of OtherNotebook in path
164 New copy of OtherNotebook in path
165 """
165 """
166
166
167 if name is not None:
167 if name is not None:
168 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
168 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
169
169
170 model = self.get_json_body()
170 model = self.get_json_body()
171
171
172 if model is not None:
172 if model is not None:
173 copy_from = model.get('copy_from')
173 copy_from = model.get('copy_from')
174 if copy_from:
174 if copy_from:
175 if model.get('content'):
175 if model.get('content'):
176 raise web.HTTPError(400, "Can't upload and copy at the same time.")
176 raise web.HTTPError(400, "Can't upload and copy at the same time.")
177 self._copy_notebook(copy_from, path)
177 self._copy_notebook(copy_from, path)
178 else:
178 else:
179 self._upload_notebook(model, path)
179 self._upload_notebook(model, path)
180 else:
180 else:
181 self._create_empty_notebook(path)
181 self._create_empty_notebook(path)
182
182
183 @web.authenticated
183 @web.authenticated
184 @json_errors
184 @json_errors
185 def put(self, path='', name=None):
185 def put(self, path='', name=None):
186 """Saves the notebook in the location specified by name and path.
186 """Saves the notebook in the location specified by name and path.
187
187
188 PUT is very similar to POST, but the requester specifies the name,
188 PUT is very similar to POST, but the requester specifies the name,
189 whereas with POST, the server picks the name.
189 whereas with POST, the server picks the name.
190
190
191 PUT /api/notebooks/path/Name.ipynb
191 PUT /api/notebooks/path/Name.ipynb
192 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
192 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
193 in `content` key of JSON request body. If content is not specified,
193 in `content` key of JSON request body. If content is not specified,
194 create a new empty notebook.
194 create a new empty notebook.
195 PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb
195 PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb
196 Copy OtherNotebook to Name
196 Copy OtherNotebook to Name
197 """
197 """
198 if name is None:
198 if name is None:
199 raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
199 raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
200
200
201 model = self.get_json_body()
201 model = self.get_json_body()
202 if model:
202 if model:
203 copy_from = model.get('copy_from')
203 copy_from = model.get('copy_from')
204 if copy_from:
204 if copy_from:
205 if model.get('content'):
205 if model.get('content'):
206 raise web.HTTPError(400, "Can't upload and copy at the same time.")
206 raise web.HTTPError(400, "Can't upload and copy at the same time.")
207 self._copy_notebook(copy_from, path, name)
207 self._copy_notebook(copy_from, path, name)
208 elif self.notebook_manager.notebook_exists(name, path):
208 elif self.notebook_manager.notebook_exists(name, path):
209 self._save_notebook(model, path, name)
209 self._save_notebook(model, path, name)
210 else:
210 else:
211 self._upload_notebook(model, path, name)
211 self._upload_notebook(model, path, name)
212 else:
212 else:
213 self._create_empty_notebook(path, name)
213 self._create_empty_notebook(path, name)
214
214
215 @web.authenticated
215 @web.authenticated
216 @json_errors
216 @json_errors
217 def delete(self, path='', name=None):
217 def delete(self, path='', name=None):
218 """delete the notebook in the given notebook path"""
218 """delete the notebook in the given notebook path"""
219 nbm = self.notebook_manager
219 nbm = self.notebook_manager
220 nbm.delete_notebook_model(name, path)
220 nbm.delete_notebook_model(name, path)
221 self.set_status(204)
221 self.set_status(204)
222 self.finish()
222 self.finish()
223
223
224
224
225 class NotebookCheckpointsHandler(IPythonHandler):
225 class NotebookCheckpointsHandler(IPythonHandler):
226
226
227 SUPPORTED_METHODS = ('GET', 'POST')
227 SUPPORTED_METHODS = ('GET', 'POST')
228
228
229 @web.authenticated
229 @web.authenticated
230 @json_errors
230 @json_errors
231 def get(self, path='', name=None):
231 def get(self, path='', name=None):
232 """get lists checkpoints for a notebook"""
232 """get lists checkpoints for a notebook"""
233 nbm = self.notebook_manager
233 nbm = self.notebook_manager
234 checkpoints = nbm.list_checkpoints(name, path)
234 checkpoints = nbm.list_checkpoints(name, path)
235 data = json.dumps(checkpoints, default=date_default)
235 data = json.dumps(checkpoints, default=date_default)
236 self.finish(data)
236 self.finish(data)
237
237
238 @web.authenticated
238 @web.authenticated
239 @json_errors
239 @json_errors
240 def post(self, path='', name=None):
240 def post(self, path='', name=None):
241 """post creates a new checkpoint"""
241 """post creates a new checkpoint"""
242 nbm = self.notebook_manager
242 nbm = self.notebook_manager
243 checkpoint = nbm.create_checkpoint(name, path)
243 checkpoint = nbm.create_checkpoint(name, path)
244 data = json.dumps(checkpoint, default=date_default)
244 data = json.dumps(checkpoint, default=date_default)
245 location = url_path_join(self.base_project_url, 'api/notebooks',
245 location = url_path_join(self.base_url, 'api/notebooks',
246 path, name, 'checkpoints', checkpoint['id'])
246 path, name, 'checkpoints', checkpoint['id'])
247 self.set_header('Location', url_escape(location))
247 self.set_header('Location', url_escape(location))
248 self.set_status(201)
248 self.set_status(201)
249 self.finish(data)
249 self.finish(data)
250
250
251
251
252 class ModifyNotebookCheckpointsHandler(IPythonHandler):
252 class ModifyNotebookCheckpointsHandler(IPythonHandler):
253
253
254 SUPPORTED_METHODS = ('POST', 'DELETE')
254 SUPPORTED_METHODS = ('POST', 'DELETE')
255
255
256 @web.authenticated
256 @web.authenticated
257 @json_errors
257 @json_errors
258 def post(self, path, name, checkpoint_id):
258 def post(self, path, name, checkpoint_id):
259 """post restores a notebook from a checkpoint"""
259 """post restores a notebook from a checkpoint"""
260 nbm = self.notebook_manager
260 nbm = self.notebook_manager
261 nbm.restore_checkpoint(checkpoint_id, name, path)
261 nbm.restore_checkpoint(checkpoint_id, name, path)
262 self.set_status(204)
262 self.set_status(204)
263 self.finish()
263 self.finish()
264
264
265 @web.authenticated
265 @web.authenticated
266 @json_errors
266 @json_errors
267 def delete(self, path, name, checkpoint_id):
267 def delete(self, path, name, checkpoint_id):
268 """delete clears a checkpoint for a given notebook"""
268 """delete clears a checkpoint for a given notebook"""
269 nbm = self.notebook_manager
269 nbm = self.notebook_manager
270 nbm.delete_checkpoint(checkpoint_id, name, path)
270 nbm.delete_checkpoint(checkpoint_id, name, path)
271 self.set_status(204)
271 self.set_status(204)
272 self.finish()
272 self.finish()
273
273
274 #-----------------------------------------------------------------------------
274 #-----------------------------------------------------------------------------
275 # URL to handler mappings
275 # URL to handler mappings
276 #-----------------------------------------------------------------------------
276 #-----------------------------------------------------------------------------
277
277
278
278
279 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
279 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
280
280
281 default_handlers = [
281 default_handlers = [
282 (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
282 (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
283 (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
283 (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
284 ModifyNotebookCheckpointsHandler),
284 ModifyNotebookCheckpointsHandler),
285 (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
285 (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
286 (r"/api/notebooks%s" % path_regex, NotebookHandler),
286 (r"/api/notebooks%s" % path_regex, NotebookHandler),
287 ]
287 ]
288
288
289
289
290
290
@@ -1,45 +1,52 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Login button
9 // Login button
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13
14
14 var LoginWidget = function (selector, options) {
15 var LoginWidget = function (selector, options) {
15 var options = options || {};
16 options = options || {};
16 this.base_url = options.baseProjectUrl || $('body').data('baseProjectUrl') ;
17 this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl");
17 this.selector = selector;
18 this.selector = selector;
18 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
19 this.element = $(selector);
20 this.element = $(selector);
20 this.style();
21 this.style();
21 this.bind_events();
22 this.bind_events();
22 }
23 }
23 };
24 };
24
25
25 LoginWidget.prototype.style = function () {
26 LoginWidget.prototype.style = function () {
26 this.element.find("button").addClass("btn btn-small");
27 this.element.find("button").addClass("btn btn-small");
27 };
28 };
28
29
29
30
30 LoginWidget.prototype.bind_events = function () {
31 LoginWidget.prototype.bind_events = function () {
31 var that = this;
32 var that = this;
32 this.element.find("button#logout").click(function () {
33 this.element.find("button#logout").click(function () {
33 window.location = that.base_url+"logout";
34 window.location = IPythin.utils.url_join_encode(
35 that.base_url,
36 "logout"
37 );
34 });
38 });
35 this.element.find("button#login").click(function () {
39 this.element.find("button#login").click(function () {
36 window.location = that.base_url+"login";
40 window.location = IPythin.utils.url_join_encode(
41 that.base_url,
42 "login"
43 );
37 });
44 });
38 };
45 };
39
46
40 // Set module variables
47 // Set module variables
41 IPython.LoginWidget = LoginWidget;
48 IPython.LoginWidget = LoginWidget;
42
49
43 return IPython;
50 return IPython;
44
51
45 }(IPython));
52 }(IPython));
@@ -1,523 +1,547 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2012 The IPython Development Team
2 // Copyright (C) 2008-2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Utilities
9 // Utilities
10 //============================================================================
10 //============================================================================
11 IPython.namespace('IPython.utils');
11 IPython.namespace('IPython.utils');
12
12
13 IPython.utils = (function (IPython) {
13 IPython.utils = (function (IPython) {
14 "use strict";
14 "use strict";
15
15
16 //============================================================================
16 //============================================================================
17 // Cross-browser RegEx Split
17 // Cross-browser RegEx Split
18 //============================================================================
18 //============================================================================
19
19
20 // This code has been MODIFIED from the code licensed below to not replace the
20 // This code has been MODIFIED from the code licensed below to not replace the
21 // default browser split. The license is reproduced here.
21 // default browser split. The license is reproduced here.
22
22
23 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
23 // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info:
24 /*!
24 /*!
25 * Cross-Browser Split 1.1.1
25 * Cross-Browser Split 1.1.1
26 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
26 * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
27 * Available under the MIT License
27 * Available under the MIT License
28 * ECMAScript compliant, uniform cross-browser split method
28 * ECMAScript compliant, uniform cross-browser split method
29 */
29 */
30
30
31 /**
31 /**
32 * Splits a string into an array of strings using a regex or string
32 * Splits a string into an array of strings using a regex or string
33 * separator. Matches of the separator are not included in the result array.
33 * separator. Matches of the separator are not included in the result array.
34 * However, if `separator` is a regex that contains capturing groups,
34 * However, if `separator` is a regex that contains capturing groups,
35 * backreferences are spliced into the result each time `separator` is
35 * backreferences are spliced into the result each time `separator` is
36 * matched. Fixes browser bugs compared to the native
36 * matched. Fixes browser bugs compared to the native
37 * `String.prototype.split` and can be used reliably cross-browser.
37 * `String.prototype.split` and can be used reliably cross-browser.
38 * @param {String} str String to split.
38 * @param {String} str String to split.
39 * @param {RegExp|String} separator Regex or string to use for separating
39 * @param {RegExp|String} separator Regex or string to use for separating
40 * the string.
40 * the string.
41 * @param {Number} [limit] Maximum number of items to include in the result
41 * @param {Number} [limit] Maximum number of items to include in the result
42 * array.
42 * array.
43 * @returns {Array} Array of substrings.
43 * @returns {Array} Array of substrings.
44 * @example
44 * @example
45 *
45 *
46 * // Basic use
46 * // Basic use
47 * regex_split('a b c d', ' ');
47 * regex_split('a b c d', ' ');
48 * // -> ['a', 'b', 'c', 'd']
48 * // -> ['a', 'b', 'c', 'd']
49 *
49 *
50 * // With limit
50 * // With limit
51 * regex_split('a b c d', ' ', 2);
51 * regex_split('a b c d', ' ', 2);
52 * // -> ['a', 'b']
52 * // -> ['a', 'b']
53 *
53 *
54 * // Backreferences in result array
54 * // Backreferences in result array
55 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
55 * regex_split('..word1 word2..', /([a-z]+)(\d+)/i);
56 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
56 * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
57 */
57 */
58 var regex_split = function (str, separator, limit) {
58 var regex_split = function (str, separator, limit) {
59 // If `separator` is not a regex, use `split`
59 // If `separator` is not a regex, use `split`
60 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
60 if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
61 return split.call(str, separator, limit);
61 return split.call(str, separator, limit);
62 }
62 }
63 var output = [],
63 var output = [],
64 flags = (separator.ignoreCase ? "i" : "") +
64 flags = (separator.ignoreCase ? "i" : "") +
65 (separator.multiline ? "m" : "") +
65 (separator.multiline ? "m" : "") +
66 (separator.extended ? "x" : "") + // Proposed for ES6
66 (separator.extended ? "x" : "") + // Proposed for ES6
67 (separator.sticky ? "y" : ""), // Firefox 3+
67 (separator.sticky ? "y" : ""), // Firefox 3+
68 lastLastIndex = 0,
68 lastLastIndex = 0,
69 // Make `global` and avoid `lastIndex` issues by working with a copy
69 // Make `global` and avoid `lastIndex` issues by working with a copy
70 separator = new RegExp(separator.source, flags + "g"),
70 separator = new RegExp(separator.source, flags + "g"),
71 separator2, match, lastIndex, lastLength;
71 separator2, match, lastIndex, lastLength;
72 str += ""; // Type-convert
72 str += ""; // Type-convert
73
73
74 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
74 var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined";
75 if (!compliantExecNpcg) {
75 if (!compliantExecNpcg) {
76 // Doesn't need flags gy, but they don't hurt
76 // Doesn't need flags gy, but they don't hurt
77 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
77 separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
78 }
78 }
79 /* Values for `limit`, per the spec:
79 /* Values for `limit`, per the spec:
80 * If undefined: 4294967295 // Math.pow(2, 32) - 1
80 * If undefined: 4294967295 // Math.pow(2, 32) - 1
81 * If 0, Infinity, or NaN: 0
81 * If 0, Infinity, or NaN: 0
82 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
82 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
83 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
83 * If negative number: 4294967296 - Math.floor(Math.abs(limit))
84 * If other: Type-convert, then use the above rules
84 * If other: Type-convert, then use the above rules
85 */
85 */
86 limit = typeof(limit) === "undefined" ?
86 limit = typeof(limit) === "undefined" ?
87 -1 >>> 0 : // Math.pow(2, 32) - 1
87 -1 >>> 0 : // Math.pow(2, 32) - 1
88 limit >>> 0; // ToUint32(limit)
88 limit >>> 0; // ToUint32(limit)
89 while (match = separator.exec(str)) {
89 while (match = separator.exec(str)) {
90 // `separator.lastIndex` is not reliable cross-browser
90 // `separator.lastIndex` is not reliable cross-browser
91 lastIndex = match.index + match[0].length;
91 lastIndex = match.index + match[0].length;
92 if (lastIndex > lastLastIndex) {
92 if (lastIndex > lastLastIndex) {
93 output.push(str.slice(lastLastIndex, match.index));
93 output.push(str.slice(lastLastIndex, match.index));
94 // Fix browsers whose `exec` methods don't consistently return `undefined` for
94 // Fix browsers whose `exec` methods don't consistently return `undefined` for
95 // nonparticipating capturing groups
95 // nonparticipating capturing groups
96 if (!compliantExecNpcg && match.length > 1) {
96 if (!compliantExecNpcg && match.length > 1) {
97 match[0].replace(separator2, function () {
97 match[0].replace(separator2, function () {
98 for (var i = 1; i < arguments.length - 2; i++) {
98 for (var i = 1; i < arguments.length - 2; i++) {
99 if (typeof(arguments[i]) === "undefined") {
99 if (typeof(arguments[i]) === "undefined") {
100 match[i] = undefined;
100 match[i] = undefined;
101 }
101 }
102 }
102 }
103 });
103 });
104 }
104 }
105 if (match.length > 1 && match.index < str.length) {
105 if (match.length > 1 && match.index < str.length) {
106 Array.prototype.push.apply(output, match.slice(1));
106 Array.prototype.push.apply(output, match.slice(1));
107 }
107 }
108 lastLength = match[0].length;
108 lastLength = match[0].length;
109 lastLastIndex = lastIndex;
109 lastLastIndex = lastIndex;
110 if (output.length >= limit) {
110 if (output.length >= limit) {
111 break;
111 break;
112 }
112 }
113 }
113 }
114 if (separator.lastIndex === match.index) {
114 if (separator.lastIndex === match.index) {
115 separator.lastIndex++; // Avoid an infinite loop
115 separator.lastIndex++; // Avoid an infinite loop
116 }
116 }
117 }
117 }
118 if (lastLastIndex === str.length) {
118 if (lastLastIndex === str.length) {
119 if (lastLength || !separator.test("")) {
119 if (lastLength || !separator.test("")) {
120 output.push("");
120 output.push("");
121 }
121 }
122 } else {
122 } else {
123 output.push(str.slice(lastLastIndex));
123 output.push(str.slice(lastLastIndex));
124 }
124 }
125 return output.length > limit ? output.slice(0, limit) : output;
125 return output.length > limit ? output.slice(0, limit) : output;
126 };
126 };
127
127
128 //============================================================================
128 //============================================================================
129 // End contributed Cross-browser RegEx Split
129 // End contributed Cross-browser RegEx Split
130 //============================================================================
130 //============================================================================
131
131
132
132
133 var uuid = function () {
133 var uuid = function () {
134 // http://www.ietf.org/rfc/rfc4122.txt
134 // http://www.ietf.org/rfc/rfc4122.txt
135 var s = [];
135 var s = [];
136 var hexDigits = "0123456789ABCDEF";
136 var hexDigits = "0123456789ABCDEF";
137 for (var i = 0; i < 32; i++) {
137 for (var i = 0; i < 32; i++) {
138 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
138 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
139 }
139 }
140 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
140 s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
141 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
141 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
142
142
143 var uuid = s.join("");
143 var uuid = s.join("");
144 return uuid;
144 return uuid;
145 };
145 };
146
146
147
147
148 //Fix raw text to parse correctly in crazy XML
148 //Fix raw text to parse correctly in crazy XML
149 function xmlencode(string) {
149 function xmlencode(string) {
150 return string.replace(/\&/g,'&'+'amp;')
150 return string.replace(/\&/g,'&'+'amp;')
151 .replace(/</g,'&'+'lt;')
151 .replace(/</g,'&'+'lt;')
152 .replace(/>/g,'&'+'gt;')
152 .replace(/>/g,'&'+'gt;')
153 .replace(/\'/g,'&'+'apos;')
153 .replace(/\'/g,'&'+'apos;')
154 .replace(/\"/g,'&'+'quot;')
154 .replace(/\"/g,'&'+'quot;')
155 .replace(/`/g,'&'+'#96;');
155 .replace(/`/g,'&'+'#96;');
156 }
156 }
157
157
158
158
159 //Map from terminal commands to CSS classes
159 //Map from terminal commands to CSS classes
160 var ansi_colormap = {
160 var ansi_colormap = {
161 "01":"ansibold",
161 "01":"ansibold",
162
162
163 "30":"ansiblack",
163 "30":"ansiblack",
164 "31":"ansired",
164 "31":"ansired",
165 "32":"ansigreen",
165 "32":"ansigreen",
166 "33":"ansiyellow",
166 "33":"ansiyellow",
167 "34":"ansiblue",
167 "34":"ansiblue",
168 "35":"ansipurple",
168 "35":"ansipurple",
169 "36":"ansicyan",
169 "36":"ansicyan",
170 "37":"ansigray",
170 "37":"ansigray",
171
171
172 "40":"ansibgblack",
172 "40":"ansibgblack",
173 "41":"ansibgred",
173 "41":"ansibgred",
174 "42":"ansibggreen",
174 "42":"ansibggreen",
175 "43":"ansibgyellow",
175 "43":"ansibgyellow",
176 "44":"ansibgblue",
176 "44":"ansibgblue",
177 "45":"ansibgpurple",
177 "45":"ansibgpurple",
178 "46":"ansibgcyan",
178 "46":"ansibgcyan",
179 "47":"ansibggray"
179 "47":"ansibggray"
180 };
180 };
181
181
182 function _process_numbers(attrs, numbers) {
182 function _process_numbers(attrs, numbers) {
183 // process ansi escapes
183 // process ansi escapes
184 var n = numbers.shift();
184 var n = numbers.shift();
185 if (ansi_colormap[n]) {
185 if (ansi_colormap[n]) {
186 if ( ! attrs["class"] ) {
186 if ( ! attrs["class"] ) {
187 attrs["class"] = ansi_colormap[n];
187 attrs["class"] = ansi_colormap[n];
188 } else {
188 } else {
189 attrs["class"] += " " + ansi_colormap[n];
189 attrs["class"] += " " + ansi_colormap[n];
190 }
190 }
191 } else if (n == "38" || n == "48") {
191 } else if (n == "38" || n == "48") {
192 // VT100 256 color or 24 bit RGB
192 // VT100 256 color or 24 bit RGB
193 if (numbers.length < 2) {
193 if (numbers.length < 2) {
194 console.log("Not enough fields for VT100 color", numbers);
194 console.log("Not enough fields for VT100 color", numbers);
195 return;
195 return;
196 }
196 }
197
197
198 var index_or_rgb = numbers.shift();
198 var index_or_rgb = numbers.shift();
199 var r,g,b;
199 var r,g,b;
200 if (index_or_rgb == "5") {
200 if (index_or_rgb == "5") {
201 // 256 color
201 // 256 color
202 var idx = parseInt(numbers.shift());
202 var idx = parseInt(numbers.shift());
203 if (idx < 16) {
203 if (idx < 16) {
204 // indexed ANSI
204 // indexed ANSI
205 // ignore bright / non-bright distinction
205 // ignore bright / non-bright distinction
206 idx = idx % 8;
206 idx = idx % 8;
207 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
207 var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
208 if ( ! attrs["class"] ) {
208 if ( ! attrs["class"] ) {
209 attrs["class"] = ansiclass;
209 attrs["class"] = ansiclass;
210 } else {
210 } else {
211 attrs["class"] += " " + ansiclass;
211 attrs["class"] += " " + ansiclass;
212 }
212 }
213 return;
213 return;
214 } else if (idx < 232) {
214 } else if (idx < 232) {
215 // 216 color 6x6x6 RGB
215 // 216 color 6x6x6 RGB
216 idx = idx - 16;
216 idx = idx - 16;
217 b = idx % 6;
217 b = idx % 6;
218 g = Math.floor(idx / 6) % 6;
218 g = Math.floor(idx / 6) % 6;
219 r = Math.floor(idx / 36) % 6;
219 r = Math.floor(idx / 36) % 6;
220 // convert to rgb
220 // convert to rgb
221 r = (r * 51);
221 r = (r * 51);
222 g = (g * 51);
222 g = (g * 51);
223 b = (b * 51);
223 b = (b * 51);
224 } else {
224 } else {
225 // grayscale
225 // grayscale
226 idx = idx - 231;
226 idx = idx - 231;
227 // it's 1-24 and should *not* include black or white,
227 // it's 1-24 and should *not* include black or white,
228 // so a 26 point scale
228 // so a 26 point scale
229 r = g = b = Math.floor(idx * 256 / 26);
229 r = g = b = Math.floor(idx * 256 / 26);
230 }
230 }
231 } else if (index_or_rgb == "2") {
231 } else if (index_or_rgb == "2") {
232 // Simple 24 bit RGB
232 // Simple 24 bit RGB
233 if (numbers.length > 3) {
233 if (numbers.length > 3) {
234 console.log("Not enough fields for RGB", numbers);
234 console.log("Not enough fields for RGB", numbers);
235 return;
235 return;
236 }
236 }
237 r = numbers.shift();
237 r = numbers.shift();
238 g = numbers.shift();
238 g = numbers.shift();
239 b = numbers.shift();
239 b = numbers.shift();
240 } else {
240 } else {
241 console.log("unrecognized control", numbers);
241 console.log("unrecognized control", numbers);
242 return;
242 return;
243 }
243 }
244 if (r !== undefined) {
244 if (r !== undefined) {
245 // apply the rgb color
245 // apply the rgb color
246 var line;
246 var line;
247 if (n == "38") {
247 if (n == "38") {
248 line = "color: ";
248 line = "color: ";
249 } else {
249 } else {
250 line = "background-color: ";
250 line = "background-color: ";
251 }
251 }
252 line = line + "rgb(" + r + "," + g + "," + b + ");"
252 line = line + "rgb(" + r + "," + g + "," + b + ");"
253 if ( !attrs["style"] ) {
253 if ( !attrs["style"] ) {
254 attrs["style"] = line;
254 attrs["style"] = line;
255 } else {
255 } else {
256 attrs["style"] += " " + line;
256 attrs["style"] += " " + line;
257 }
257 }
258 }
258 }
259 }
259 }
260 }
260 }
261
261
262 function ansispan(str) {
262 function ansispan(str) {
263 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
263 // ansispan function adapted from github.com/mmalecki/ansispan (MIT License)
264 // regular ansi escapes (using the table above)
264 // regular ansi escapes (using the table above)
265 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
265 return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
266 if (!pattern) {
266 if (!pattern) {
267 // [(01|22|39|)m close spans
267 // [(01|22|39|)m close spans
268 return "</span>";
268 return "</span>";
269 }
269 }
270 // consume sequence of color escapes
270 // consume sequence of color escapes
271 var numbers = pattern.match(/\d+/g);
271 var numbers = pattern.match(/\d+/g);
272 var attrs = {};
272 var attrs = {};
273 while (numbers.length > 0) {
273 while (numbers.length > 0) {
274 _process_numbers(attrs, numbers);
274 _process_numbers(attrs, numbers);
275 }
275 }
276
276
277 var span = "<span ";
277 var span = "<span ";
278 for (var attr in attrs) {
278 for (var attr in attrs) {
279 var value = attrs[attr];
279 var value = attrs[attr];
280 span = span + " " + attr + '="' + attrs[attr] + '"';
280 span = span + " " + attr + '="' + attrs[attr] + '"';
281 }
281 }
282 return span + ">";
282 return span + ">";
283 });
283 });
284 };
284 };
285
285
286 // Transform ANSI color escape codes into HTML <span> tags with css
286 // Transform ANSI color escape codes into HTML <span> tags with css
287 // classes listed in the above ansi_colormap object. The actual color used
287 // classes listed in the above ansi_colormap object. The actual color used
288 // are set in the css file.
288 // are set in the css file.
289 function fixConsole(txt) {
289 function fixConsole(txt) {
290 txt = xmlencode(txt);
290 txt = xmlencode(txt);
291 var re = /\033\[([\dA-Fa-f;]*?)m/;
291 var re = /\033\[([\dA-Fa-f;]*?)m/;
292 var opened = false;
292 var opened = false;
293 var cmds = [];
293 var cmds = [];
294 var opener = "";
294 var opener = "";
295 var closer = "";
295 var closer = "";
296
296
297 // Strip all ANSI codes that are not color related. Matches
297 // Strip all ANSI codes that are not color related. Matches
298 // all ANSI codes that do not end with "m".
298 // all ANSI codes that do not end with "m".
299 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
299 var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
300 txt = txt.replace(ignored_re, "");
300 txt = txt.replace(ignored_re, "");
301
301
302 // color ansi codes
302 // color ansi codes
303 txt = ansispan(txt);
303 txt = ansispan(txt);
304 return txt;
304 return txt;
305 }
305 }
306
306
307 // Remove chunks that should be overridden by the effect of
307 // Remove chunks that should be overridden by the effect of
308 // carriage return characters
308 // carriage return characters
309 function fixCarriageReturn(txt) {
309 function fixCarriageReturn(txt) {
310 var tmp = txt;
310 var tmp = txt;
311 do {
311 do {
312 txt = tmp;
312 txt = tmp;
313 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
313 tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
314 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
314 tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line
315 } while (tmp.length < txt.length);
315 } while (tmp.length < txt.length);
316 return txt;
316 return txt;
317 }
317 }
318
318
319 // Locate any URLs and convert them to a anchor tag
319 // Locate any URLs and convert them to a anchor tag
320 function autoLinkUrls(txt) {
320 function autoLinkUrls(txt) {
321 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
321 return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
322 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
322 "$1<a target=\"_blank\" href=\"$2$3\">$2$3</a>");
323 }
323 }
324
324
325 // some keycodes that seem to be platform/browser independent
325 // some keycodes that seem to be platform/browser independent
326 var keycodes = {
326 var keycodes = {
327 BACKSPACE: 8,
327 BACKSPACE: 8,
328 TAB : 9,
328 TAB : 9,
329 ENTER : 13,
329 ENTER : 13,
330 SHIFT : 16,
330 SHIFT : 16,
331 CTRL : 17,
331 CTRL : 17,
332 CONTROL : 17,
332 CONTROL : 17,
333 ALT : 18,
333 ALT : 18,
334 CAPS_LOCK: 20,
334 CAPS_LOCK: 20,
335 ESC : 27,
335 ESC : 27,
336 SPACE : 32,
336 SPACE : 32,
337 PGUP : 33,
337 PGUP : 33,
338 PGDOWN : 34,
338 PGDOWN : 34,
339 END : 35,
339 END : 35,
340 HOME : 36,
340 HOME : 36,
341 LEFT_ARROW: 37,
341 LEFT_ARROW: 37,
342 LEFTARROW: 37,
342 LEFTARROW: 37,
343 LEFT : 37,
343 LEFT : 37,
344 UP_ARROW : 38,
344 UP_ARROW : 38,
345 UPARROW : 38,
345 UPARROW : 38,
346 UP : 38,
346 UP : 38,
347 RIGHT_ARROW:39,
347 RIGHT_ARROW:39,
348 RIGHTARROW:39,
348 RIGHTARROW:39,
349 RIGHT : 39,
349 RIGHT : 39,
350 DOWN_ARROW: 40,
350 DOWN_ARROW: 40,
351 DOWNARROW: 40,
351 DOWNARROW: 40,
352 DOWN : 40,
352 DOWN : 40,
353 I : 73,
353 I : 73,
354 M : 77,
354 M : 77,
355 // all three of these keys may be COMMAND on OS X:
355 // all three of these keys may be COMMAND on OS X:
356 LEFT_SUPER : 91,
356 LEFT_SUPER : 91,
357 RIGHT_SUPER : 92,
357 RIGHT_SUPER : 92,
358 COMMAND : 93,
358 COMMAND : 93,
359 };
359 };
360
360
361 // trigger a key press event
361 // trigger a key press event
362 var press = function (key) {
362 var press = function (key) {
363 var key_press = $.Event('keydown', {which: key});
363 var key_press = $.Event('keydown', {which: key});
364 $(document).trigger(key_press);
364 $(document).trigger(key_press);
365 }
365 }
366
366
367 var press_up = function() { press(keycodes.UP); };
367 var press_up = function() { press(keycodes.UP); };
368 var press_down = function() { press(keycodes.DOWN); };
368 var press_down = function() { press(keycodes.DOWN); };
369
369
370 var press_ctrl_enter = function() {
370 var press_ctrl_enter = function() {
371 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, ctrlKey: true}));
371 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, ctrlKey: true}));
372 };
372 };
373
373
374 var press_shift_enter = function() {
374 var press_shift_enter = function() {
375 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, shiftKey: true}));
375 $(document).trigger($.Event('keydown', {which: keycodes.ENTER, shiftKey: true}));
376 };
376 };
377
377
378 // trigger the ctrl-m shortcut followed by one of our keys
378 // trigger the ctrl-m shortcut followed by one of our keys
379 var press_ghetto = function(key) {
379 var press_ghetto = function(key) {
380 $(document).trigger($.Event('keydown', {which: keycodes.M, ctrlKey: true}));
380 $(document).trigger($.Event('keydown', {which: keycodes.M, ctrlKey: true}));
381 press(key);
381 press(key);
382 };
382 };
383
383
384
384
385 var points_to_pixels = function (points) {
385 var points_to_pixels = function (points) {
386 // A reasonably good way of converting between points and pixels.
386 // A reasonably good way of converting between points and pixels.
387 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
387 var test = $('<div style="display: none; width: 10000pt; padding:0; border:0;"></div>');
388 $(body).append(test);
388 $(body).append(test);
389 var pixel_per_point = test.width()/10000;
389 var pixel_per_point = test.width()/10000;
390 test.remove();
390 test.remove();
391 return Math.floor(points*pixel_per_point);
391 return Math.floor(points*pixel_per_point);
392 };
392 };
393
393
394 var always_new = function (constructor) {
394 var always_new = function (constructor) {
395 // wrapper around contructor to avoid requiring `var a = new constructor()`
395 // wrapper around contructor to avoid requiring `var a = new constructor()`
396 // useful for passing constructors as callbacks,
396 // useful for passing constructors as callbacks,
397 // not for programmer laziness.
397 // not for programmer laziness.
398 // from http://programmers.stackexchange.com/questions/118798
398 // from http://programmers.stackexchange.com/questions/118798
399 return function () {
399 return function () {
400 var obj = Object.create(constructor.prototype);
400 var obj = Object.create(constructor.prototype);
401 constructor.apply(obj, arguments);
401 constructor.apply(obj, arguments);
402 return obj;
402 return obj;
403 };
403 };
404 };
404 };
405
405
406
406
407 var url_path_join = function () {
407 var url_path_join = function () {
408 // join a sequence of url components with '/'
408 // join a sequence of url components with '/'
409 var url = '';
409 var url = '';
410 for (var i = 0; i < arguments.length; i++) {
410 for (var i = 0; i < arguments.length; i++) {
411 if (arguments[i] === '') {
411 if (arguments[i] === '') {
412 continue;
412 continue;
413 }
413 }
414 if (url.length > 0 && url[url.length-1] != '/') {
414 if (url.length > 0 && url[url.length-1] != '/') {
415 url = url + '/' + arguments[i];
415 url = url + '/' + arguments[i];
416 } else {
416 } else {
417 url = url + arguments[i];
417 url = url + arguments[i];
418 }
418 }
419 }
419 }
420 url = url.replace(/\/\/+/, '/');
420 return url;
421 return url;
421 };
422 };
422
423
424 var parse_url = function (url) {
425 // an `a` element with an href allows attr-access to the parsed segments of a URL
426 // a = parse_url("http://localhost:8888/path/name#hash")
427 // a.protocol = "http:"
428 // a.host = "localhost:8888"
429 // a.hostname = "localhost"
430 // a.port = 8888
431 // a.pathname = "/path/name"
432 // a.hash = "#hash"
433 var a = document.createElement("a");
434 a.href = url;
435 return a;
436 };
423
437
424 var encode_uri_components = function (uri) {
438 var encode_uri_components = function (uri) {
425 // encode just the components of a multi-segment uri,
439 // encode just the components of a multi-segment uri,
426 // leaving '/' separators
440 // leaving '/' separators
427 return uri.split('/').map(encodeURIComponent).join('/');
441 return uri.split('/').map(encodeURIComponent).join('/');
428 }
442 };
429
443
430 var url_join_encode = function () {
444 var url_join_encode = function () {
431 // join a sequence of url components with '/',
445 // join a sequence of url components with '/',
432 // encoding each component with encodeURIComponent
446 // encoding each component with encodeURIComponent
433 return encode_uri_components(url_path_join.apply(null, arguments));
447 return encode_uri_components(url_path_join.apply(null, arguments));
434 };
448 };
435
449
436
450
437 var splitext = function (filename) {
451 var splitext = function (filename) {
438 // mimic Python os.path.splitext
452 // mimic Python os.path.splitext
439 // Returns ['base', '.ext']
453 // Returns ['base', '.ext']
440 var idx = filename.lastIndexOf('.');
454 var idx = filename.lastIndexOf('.');
441 if (idx > 0) {
455 if (idx > 0) {
442 return [filename.slice(0, idx), filename.slice(idx)];
456 return [filename.slice(0, idx), filename.slice(idx)];
443 } else {
457 } else {
444 return [filename, ''];
458 return [filename, ''];
445 }
459 }
446 }
460 };
461
462
463 var get_body_data = function(key) {
464 // get a url-encoded item from body.data and decode it
465 // we should never have any encoded URLs anywhere else in code
466 // until we are building an actual request
467 return decodeURIComponent($('body').data(key));
468 };
447
469
448
470
449 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
471 // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript
450 var browser = (function() {
472 var browser = (function() {
451 if (typeof navigator === 'undefined') {
473 if (typeof navigator === 'undefined') {
452 // navigator undefined in node
474 // navigator undefined in node
453 return 'None';
475 return 'None';
454 }
476 }
455 var N= navigator.appName, ua= navigator.userAgent, tem;
477 var N= navigator.appName, ua= navigator.userAgent, tem;
456 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
478 var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
457 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
479 if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
458 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
480 M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
459 return M;
481 return M;
460 })();
482 })();
461
483
462 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
484 // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript
463 var platform = (function () {
485 var platform = (function () {
464 if (typeof navigator === 'undefined') {
486 if (typeof navigator === 'undefined') {
465 // navigator undefined in node
487 // navigator undefined in node
466 return 'None';
488 return 'None';
467 }
489 }
468 var OSName="None";
490 var OSName="None";
469 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
491 if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows";
470 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
492 if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS";
471 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
493 if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX";
472 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
494 if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux";
473 return OSName
495 return OSName
474 })();
496 })();
475
497
476 var is_or_has = function (a, b) {
498 var is_or_has = function (a, b) {
477 // Is b a child of a or a itself?
499 // Is b a child of a or a itself?
478 return a.has(b).length !==0 || a.is(b);
500 return a.has(b).length !==0 || a.is(b);
479 }
501 }
480
502
481 var is_focused = function (e) {
503 var is_focused = function (e) {
482 // Is element e, or one of its children focused?
504 // Is element e, or one of its children focused?
483 e = $(e);
505 e = $(e);
484 var target = $(document.activeElement);
506 var target = $(document.activeElement);
485 if (target.length > 0) {
507 if (target.length > 0) {
486 if (is_or_has(e, target)) {
508 if (is_or_has(e, target)) {
487 return true;
509 return true;
488 } else {
510 } else {
489 return false;
511 return false;
490 }
512 }
491 } else {
513 } else {
492 return false;
514 return false;
493 }
515 }
494 }
516 }
495
517
496
518
497 return {
519 return {
498 regex_split : regex_split,
520 regex_split : regex_split,
499 uuid : uuid,
521 uuid : uuid,
500 fixConsole : fixConsole,
522 fixConsole : fixConsole,
501 keycodes : keycodes,
523 keycodes : keycodes,
502 press : press,
524 press : press,
503 press_up : press_up,
525 press_up : press_up,
504 press_down : press_down,
526 press_down : press_down,
505 press_ctrl_enter : press_ctrl_enter,
527 press_ctrl_enter : press_ctrl_enter,
506 press_shift_enter : press_shift_enter,
528 press_shift_enter : press_shift_enter,
507 press_ghetto : press_ghetto,
529 press_ghetto : press_ghetto,
508 fixCarriageReturn : fixCarriageReturn,
530 fixCarriageReturn : fixCarriageReturn,
509 autoLinkUrls : autoLinkUrls,
531 autoLinkUrls : autoLinkUrls,
510 points_to_pixels : points_to_pixels,
532 points_to_pixels : points_to_pixels,
533 get_body_data : get_body_data,
534 parse_url : parse_url,
511 url_path_join : url_path_join,
535 url_path_join : url_path_join,
512 url_join_encode : url_join_encode,
536 url_join_encode : url_join_encode,
513 encode_uri_components : encode_uri_components,
537 encode_uri_components : encode_uri_components,
514 splitext : splitext,
538 splitext : splitext,
515 always_new : always_new,
539 always_new : always_new,
516 browser : browser,
540 browser : browser,
517 platform: platform,
541 platform: platform,
518 is_or_has : is_or_has,
542 is_or_has : is_or_has,
519 is_focused : is_focused
543 is_focused : is_focused
520 };
544 };
521
545
522 }(IPython));
546 }(IPython));
523
547
@@ -1,764 +1,772 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Keyboard management
9 // Keyboard management
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 // Setup global keycodes and inverse keycodes.
15 // Setup global keycodes and inverse keycodes.
16
16
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
20 // but have minor differences.
20 // but have minor differences.
21
21
22 // These apply to Firefox, (Webkit and IE)
22 // These apply to Firefox, (Webkit and IE)
23 var _keycodes = {
23 var _keycodes = {
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
30 '\\ |': 220, '\' "': 222,
30 '\\ |': 220, '\' "': 222,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
39 'insert': 45, 'delete': 46, 'numlock': 144,
39 'insert': 45, 'delete': 46, 'numlock': 144,
40 };
40 };
41
41
42 // These apply to Firefox and Opera
42 // These apply to Firefox and Opera
43 var _mozilla_keycodes = {
43 var _mozilla_keycodes = {
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
45 }
45 }
46
46
47 // This apply to Webkit and IE
47 // This apply to Webkit and IE
48 var _ie_keycodes = {
48 var _ie_keycodes = {
49 '; :': 186, '= +': 187, '- _': 189,
49 '; :': 186, '= +': 187, '- _': 189,
50 }
50 }
51
51
52 var browser = IPython.utils.browser[0];
52 var browser = IPython.utils.browser[0];
53 var platform = IPython.utils.platform;
53 var platform = IPython.utils.platform;
54
54
55 if (browser === 'Firefox' || browser === 'Opera') {
55 if (browser === 'Firefox' || browser === 'Opera') {
56 $.extend(_keycodes, _mozilla_keycodes);
56 $.extend(_keycodes, _mozilla_keycodes);
57 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
57 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
58 $.extend(_keycodes, _ie_keycodes);
58 $.extend(_keycodes, _ie_keycodes);
59 }
59 }
60
60
61 var keycodes = {};
61 var keycodes = {};
62 var inv_keycodes = {};
62 var inv_keycodes = {};
63 for (var name in _keycodes) {
63 for (var name in _keycodes) {
64 var names = name.split(' ');
64 var names = name.split(' ');
65 if (names.length === 1) {
65 if (names.length === 1) {
66 var n = names[0]
66 var n = names[0]
67 keycodes[n] = _keycodes[n]
67 keycodes[n] = _keycodes[n]
68 inv_keycodes[_keycodes[n]] = n
68 inv_keycodes[_keycodes[n]] = n
69 } else {
69 } else {
70 var primary = names[0];
70 var primary = names[0];
71 var secondary = names[1];
71 var secondary = names[1];
72 keycodes[primary] = _keycodes[name]
72 keycodes[primary] = _keycodes[name]
73 keycodes[secondary] = _keycodes[name]
73 keycodes[secondary] = _keycodes[name]
74 inv_keycodes[_keycodes[name]] = primary
74 inv_keycodes[_keycodes[name]] = primary
75 }
75 }
76 }
76 }
77
77
78
78
79 // Default keyboard shortcuts
79 // Default keyboard shortcuts
80
80
81 var default_common_shortcuts = {
81 var default_common_shortcuts = {
82 'shift' : {
82 'shift' : {
83 help : '',
83 help : '',
84 help_index : '',
84 help_index : '',
85 handler : function (event) {
85 handler : function (event) {
86 // ignore shift keydown
86 // ignore shift keydown
87 return true;
87 return true;
88 }
88 }
89 },
89 },
90 'shift+enter' : {
90 'shift+enter' : {
91 help : 'run cell, select below',
91 help : 'run cell, select below',
92 help_index : 'ba',
92 help_index : 'ba',
93 handler : function (event) {
93 handler : function (event) {
94 IPython.notebook.execute_cell_and_select_below();
94 IPython.notebook.execute_cell_and_select_below();
95 return false;
95 return false;
96 }
96 }
97 },
97 },
98 'ctrl+enter' : {
98 'ctrl+enter' : {
99 help : 'run cell',
99 help : 'run cell',
100 help_index : 'bb',
100 help_index : 'bb',
101 handler : function (event) {
101 handler : function (event) {
102 IPython.notebook.execute_cell();
102 IPython.notebook.execute_cell();
103 return false;
103 return false;
104 }
104 }
105 },
105 },
106 'alt+enter' : {
106 'alt+enter' : {
107 help : 'run cell, insert below',
107 help : 'run cell, insert below',
108 help_index : 'bc',
108 help_index : 'bc',
109 handler : function (event) {
109 handler : function (event) {
110 IPython.notebook.execute_cell_and_insert_below();
110 IPython.notebook.execute_cell_and_insert_below();
111 return false;
111 return false;
112 }
112 }
113 }
113 }
114 }
114 }
115
115
116 if (platform === 'MacOS') {
116 if (platform === 'MacOS') {
117 default_common_shortcuts['cmd+s'] =
117 default_common_shortcuts['cmd+s'] =
118 {
118 {
119 help : 'save notebook',
119 help : 'save notebook',
120 help_index : 'fb',
120 help_index : 'fb',
121 handler : function (event) {
121 handler : function (event) {
122 IPython.notebook.save_checkpoint();
122 IPython.notebook.save_checkpoint();
123 event.preventDefault();
123 event.preventDefault();
124 return false;
124 return false;
125 }
125 }
126 };
126 };
127 } else {
127 } else {
128 default_common_shortcuts['ctrl+s'] =
128 default_common_shortcuts['ctrl+s'] =
129 {
129 {
130 help : 'save notebook',
130 help : 'save notebook',
131 help_index : 'fb',
131 help_index : 'fb',
132 handler : function (event) {
132 handler : function (event) {
133 IPython.notebook.save_checkpoint();
133 IPython.notebook.save_checkpoint();
134 event.preventDefault();
134 event.preventDefault();
135 return false;
135 return false;
136 }
136 }
137 };
137 };
138 }
138 }
139
139
140 // Edit mode defaults
140 // Edit mode defaults
141
141
142 var default_edit_shortcuts = {
142 var default_edit_shortcuts = {
143 'esc' : {
143 'esc' : {
144 help : 'command mode',
144 help : 'command mode',
145 help_index : 'aa',
145 help_index : 'aa',
146 handler : function (event) {
146 handler : function (event) {
147 IPython.notebook.command_mode();
147 IPython.notebook.command_mode();
148 IPython.notebook.focus_cell();
148 IPython.notebook.focus_cell();
149 return false;
149 return false;
150 }
150 }
151 },
151 },
152 'ctrl+m' : {
152 'ctrl+m' : {
153 help : 'command mode',
153 help : 'command mode',
154 help_index : 'ab',
154 help_index : 'ab',
155 handler : function (event) {
155 handler : function (event) {
156 IPython.notebook.command_mode();
156 IPython.notebook.command_mode();
157 IPython.notebook.focus_cell();
157 IPython.notebook.focus_cell();
158 return false;
158 return false;
159 }
159 }
160 },
160 },
161 'up' : {
161 'up' : {
162 help : '',
162 help : '',
163 help_index : '',
163 help_index : '',
164 handler : function (event) {
164 handler : function (event) {
165 var cell = IPython.notebook.get_selected_cell();
165 var cell = IPython.notebook.get_selected_cell();
166 if (cell && cell.at_top()) {
166 if (cell && cell.at_top()) {
167 event.preventDefault();
167 event.preventDefault();
168 IPython.notebook.command_mode()
168 IPython.notebook.command_mode()
169 IPython.notebook.select_prev();
169 IPython.notebook.select_prev();
170 IPython.notebook.edit_mode();
170 IPython.notebook.edit_mode();
171 return false;
171 return false;
172 };
172 };
173 }
173 }
174 },
174 },
175 'down' : {
175 'down' : {
176 help : '',
176 help : '',
177 help_index : '',
177 help_index : '',
178 handler : function (event) {
178 handler : function (event) {
179 var cell = IPython.notebook.get_selected_cell();
179 var cell = IPython.notebook.get_selected_cell();
180 if (cell && cell.at_bottom()) {
180 if (cell && cell.at_bottom()) {
181 event.preventDefault();
181 event.preventDefault();
182 IPython.notebook.command_mode()
182 IPython.notebook.command_mode()
183 IPython.notebook.select_next();
183 IPython.notebook.select_next();
184 IPython.notebook.edit_mode();
184 IPython.notebook.edit_mode();
185 return false;
185 return false;
186 };
186 };
187 }
187 }
188 },
188 },
189 'alt+-' : {
189 'alt+-' : {
190 help : 'split cell',
190 help : 'split cell',
191 help_index : 'ea',
191 help_index : 'ea',
192 handler : function (event) {
192 handler : function (event) {
193 IPython.notebook.split_cell();
193 IPython.notebook.split_cell();
194 return false;
194 return false;
195 }
195 }
196 },
196 },
197 'alt+subtract' : {
197 'alt+subtract' : {
198 help : '',
198 help : '',
199 help_index : 'eb',
199 help_index : 'eb',
200 handler : function (event) {
200 handler : function (event) {
201 IPython.notebook.split_cell();
201 IPython.notebook.split_cell();
202 return false;
202 return false;
203 }
203 }
204 },
204 },
205 'tab' : {
205 'tab' : {
206 help : 'indent or complete',
206 help : 'indent or complete',
207 help_index : 'ec',
207 help_index : 'ec',
208 },
208 },
209 'shift+tab' : {
209 'shift+tab' : {
210 help : 'tooltip',
210 help : 'tooltip',
211 help_index : 'ed',
211 help_index : 'ed',
212 },
212 },
213 }
213 }
214
214
215 if (platform === 'MacOS') {
215 if (platform === 'MacOS') {
216 default_edit_shortcuts['cmd+/'] =
216 default_edit_shortcuts['cmd+/'] =
217 {
217 {
218 help : 'toggle comment',
218 help : 'toggle comment',
219 help_index : 'ee'
219 help_index : 'ee'
220 };
220 };
221 default_edit_shortcuts['cmd+]'] =
221 default_edit_shortcuts['cmd+]'] =
222 {
222 {
223 help : 'indent',
223 help : 'indent',
224 help_index : 'ef'
224 help_index : 'ef'
225 };
225 };
226 default_edit_shortcuts['cmd+['] =
226 default_edit_shortcuts['cmd+['] =
227 {
227 {
228 help : 'dedent',
228 help : 'dedent',
229 help_index : 'eg'
229 help_index : 'eg'
230 };
230 };
231 } else {
231 } else {
232 default_edit_shortcuts['ctrl+/'] =
232 default_edit_shortcuts['ctrl+/'] =
233 {
233 {
234 help : 'toggle comment',
234 help : 'toggle comment',
235 help_index : 'ee'
235 help_index : 'ee'
236 };
236 };
237 default_edit_shortcuts['ctrl+]'] =
237 default_edit_shortcuts['ctrl+]'] =
238 {
238 {
239 help : 'indent',
239 help : 'indent',
240 help_index : 'ef'
240 help_index : 'ef'
241 };
241 };
242 default_edit_shortcuts['ctrl+['] =
242 default_edit_shortcuts['ctrl+['] =
243 {
243 {
244 help : 'dedent',
244 help : 'dedent',
245 help_index : 'eg'
245 help_index : 'eg'
246 };
246 };
247 }
247 }
248
248
249 // Command mode defaults
249 // Command mode defaults
250
250
251 var default_command_shortcuts = {
251 var default_command_shortcuts = {
252 'enter' : {
252 'enter' : {
253 help : 'edit mode',
253 help : 'edit mode',
254 help_index : 'aa',
254 help_index : 'aa',
255 handler : function (event) {
255 handler : function (event) {
256 IPython.notebook.edit_mode();
256 IPython.notebook.edit_mode();
257 return false;
257 return false;
258 }
258 }
259 },
259 },
260 'up' : {
260 'up' : {
261 help : 'select previous cell',
261 help : 'select previous cell',
262 help_index : 'da',
262 help_index : 'da',
263 handler : function (event) {
263 handler : function (event) {
264 var index = IPython.notebook.get_selected_index();
264 var index = IPython.notebook.get_selected_index();
265 if (index !== 0 && index !== null) {
265 if (index !== 0 && index !== null) {
266 IPython.notebook.select_prev();
266 IPython.notebook.select_prev();
267 var cell = IPython.notebook.get_selected_cell();
267 var cell = IPython.notebook.get_selected_cell();
268 cell.focus_cell();
268 cell.focus_cell();
269 };
269 };
270 return false;
270 return false;
271 }
271 }
272 },
272 },
273 'down' : {
273 'down' : {
274 help : 'select next cell',
274 help : 'select next cell',
275 help_index : 'db',
275 help_index : 'db',
276 handler : function (event) {
276 handler : function (event) {
277 var index = IPython.notebook.get_selected_index();
277 var index = IPython.notebook.get_selected_index();
278 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
278 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
279 IPython.notebook.select_next();
279 IPython.notebook.select_next();
280 var cell = IPython.notebook.get_selected_cell();
280 var cell = IPython.notebook.get_selected_cell();
281 cell.focus_cell();
281 cell.focus_cell();
282 };
282 };
283 return false;
283 return false;
284 }
284 }
285 },
285 },
286 'k' : {
286 'k' : {
287 help : 'select previous cell',
287 help : 'select previous cell',
288 help_index : 'dc',
288 help_index : 'dc',
289 handler : function (event) {
289 handler : function (event) {
290 var index = IPython.notebook.get_selected_index();
290 var index = IPython.notebook.get_selected_index();
291 if (index !== 0 && index !== null) {
291 if (index !== 0 && index !== null) {
292 IPython.notebook.select_prev();
292 IPython.notebook.select_prev();
293 var cell = IPython.notebook.get_selected_cell();
293 var cell = IPython.notebook.get_selected_cell();
294 cell.focus_cell();
294 cell.focus_cell();
295 };
295 };
296 return false;
296 return false;
297 }
297 }
298 },
298 },
299 'j' : {
299 'j' : {
300 help : 'select next cell',
300 help : 'select next cell',
301 help_index : 'dd',
301 help_index : 'dd',
302 handler : function (event) {
302 handler : function (event) {
303 var index = IPython.notebook.get_selected_index();
303 var index = IPython.notebook.get_selected_index();
304 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
304 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
305 IPython.notebook.select_next();
305 IPython.notebook.select_next();
306 var cell = IPython.notebook.get_selected_cell();
306 var cell = IPython.notebook.get_selected_cell();
307 cell.focus_cell();
307 cell.focus_cell();
308 };
308 };
309 return false;
309 return false;
310 }
310 }
311 },
311 },
312 'x' : {
312 'x' : {
313 help : 'cut cell',
313 help : 'cut cell',
314 help_index : 'ee',
314 help_index : 'ee',
315 handler : function (event) {
315 handler : function (event) {
316 IPython.notebook.cut_cell();
316 IPython.notebook.cut_cell();
317 return false;
317 return false;
318 }
318 }
319 },
319 },
320 'c' : {
320 'c' : {
321 help : 'copy cell',
321 help : 'copy cell',
322 help_index : 'ef',
322 help_index : 'ef',
323 handler : function (event) {
323 handler : function (event) {
324 IPython.notebook.copy_cell();
324 IPython.notebook.copy_cell();
325 return false;
325 return false;
326 }
326 }
327 },
327 },
328 'shift+v' : {
328 'shift+v' : {
329 help : 'paste cell above',
329 help : 'paste cell above',
330 help_index : 'eg',
330 help_index : 'eg',
331 handler : function (event) {
331 handler : function (event) {
332 IPython.notebook.paste_cell_above();
332 IPython.notebook.paste_cell_above();
333 return false;
333 return false;
334 }
334 }
335 },
335 },
336 'v' : {
336 'v' : {
337 help : 'paste cell below',
337 help : 'paste cell below',
338 help_index : 'eh',
338 help_index : 'eh',
339 handler : function (event) {
339 handler : function (event) {
340 IPython.notebook.paste_cell_below();
340 IPython.notebook.paste_cell_below();
341 return false;
341 return false;
342 }
342 }
343 },
343 },
344 'd' : {
344 'd' : {
345 help : 'delete cell (press twice)',
345 help : 'delete cell (press twice)',
346 help_index : 'ej',
346 help_index : 'ej',
347 count: 2,
347 count: 2,
348 handler : function (event) {
348 handler : function (event) {
349 IPython.notebook.delete_cell();
349 IPython.notebook.delete_cell();
350 return false;
350 return false;
351 }
351 }
352 },
352 },
353 'a' : {
353 'a' : {
354 help : 'insert cell above',
354 help : 'insert cell above',
355 help_index : 'ec',
355 help_index : 'ec',
356 handler : function (event) {
356 handler : function (event) {
357 IPython.notebook.insert_cell_above('code');
357 IPython.notebook.insert_cell_above('code');
358 IPython.notebook.select_prev();
358 IPython.notebook.select_prev();
359 IPython.notebook.focus_cell();
359 IPython.notebook.focus_cell();
360 return false;
360 return false;
361 }
361 }
362 },
362 },
363 'b' : {
363 'b' : {
364 help : 'insert cell below',
364 help : 'insert cell below',
365 help_index : 'ed',
365 help_index : 'ed',
366 handler : function (event) {
366 handler : function (event) {
367 IPython.notebook.insert_cell_below('code');
367 IPython.notebook.insert_cell_below('code');
368 IPython.notebook.select_next();
368 IPython.notebook.select_next();
369 IPython.notebook.focus_cell();
369 IPython.notebook.focus_cell();
370 return false;
370 return false;
371 }
371 }
372 },
372 },
373 'y' : {
373 'y' : {
374 help : 'to code',
374 help : 'to code',
375 help_index : 'ca',
375 help_index : 'ca',
376 handler : function (event) {
376 handler : function (event) {
377 IPython.notebook.to_code();
377 IPython.notebook.to_code();
378 return false;
378 return false;
379 }
379 }
380 },
380 },
381 'm' : {
381 'm' : {
382 help : 'to markdown',
382 help : 'to markdown',
383 help_index : 'cb',
383 help_index : 'cb',
384 handler : function (event) {
384 handler : function (event) {
385 IPython.notebook.to_markdown();
385 IPython.notebook.to_markdown();
386 return false;
386 return false;
387 }
387 }
388 },
388 },
389 'r' : {
389 'r' : {
390 help : 'to raw',
390 help : 'to raw',
391 help_index : 'cc',
391 help_index : 'cc',
392 handler : function (event) {
392 handler : function (event) {
393 IPython.notebook.to_raw();
393 IPython.notebook.to_raw();
394 return false;
394 return false;
395 }
395 }
396 },
396 },
397 '1' : {
397 '1' : {
398 help : 'to heading 1',
398 help : 'to heading 1',
399 help_index : 'cd',
399 help_index : 'cd',
400 handler : function (event) {
400 handler : function (event) {
401 IPython.notebook.to_heading(undefined, 1);
401 IPython.notebook.to_heading(undefined, 1);
402 return false;
402 return false;
403 }
403 }
404 },
404 },
405 '2' : {
405 '2' : {
406 help : 'to heading 2',
406 help : 'to heading 2',
407 help_index : 'ce',
407 help_index : 'ce',
408 handler : function (event) {
408 handler : function (event) {
409 IPython.notebook.to_heading(undefined, 2);
409 IPython.notebook.to_heading(undefined, 2);
410 return false;
410 return false;
411 }
411 }
412 },
412 },
413 '3' : {
413 '3' : {
414 help : 'to heading 3',
414 help : 'to heading 3',
415 help_index : 'cf',
415 help_index : 'cf',
416 handler : function (event) {
416 handler : function (event) {
417 IPython.notebook.to_heading(undefined, 3);
417 IPython.notebook.to_heading(undefined, 3);
418 return false;
418 return false;
419 }
419 }
420 },
420 },
421 '4' : {
421 '4' : {
422 help : 'to heading 4',
422 help : 'to heading 4',
423 help_index : 'cg',
423 help_index : 'cg',
424 handler : function (event) {
424 handler : function (event) {
425 IPython.notebook.to_heading(undefined, 4);
425 IPython.notebook.to_heading(undefined, 4);
426 return false;
426 return false;
427 }
427 }
428 },
428 },
429 '5' : {
429 '5' : {
430 help : 'to heading 5',
430 help : 'to heading 5',
431 help_index : 'ch',
431 help_index : 'ch',
432 handler : function (event) {
432 handler : function (event) {
433 IPython.notebook.to_heading(undefined, 5);
433 IPython.notebook.to_heading(undefined, 5);
434 return false;
434 return false;
435 }
435 }
436 },
436 },
437 '6' : {
437 '6' : {
438 help : 'to heading 6',
438 help : 'to heading 6',
439 help_index : 'ci',
439 help_index : 'ci',
440 handler : function (event) {
440 handler : function (event) {
441 IPython.notebook.to_heading(undefined, 6);
441 IPython.notebook.to_heading(undefined, 6);
442 return false;
442 return false;
443 }
443 }
444 },
444 },
445 'o' : {
445 'o' : {
446 help : 'toggle output',
446 help : 'toggle output',
447 help_index : 'gb',
447 help_index : 'gb',
448 handler : function (event) {
448 handler : function (event) {
449 IPython.notebook.toggle_output();
449 IPython.notebook.toggle_output();
450 return false;
450 return false;
451 }
451 }
452 },
452 },
453 'shift+o' : {
453 'shift+o' : {
454 help : 'toggle output scrolling',
454 help : 'toggle output scrolling',
455 help_index : 'gc',
455 help_index : 'gc',
456 handler : function (event) {
456 handler : function (event) {
457 IPython.notebook.toggle_output_scroll();
457 IPython.notebook.toggle_output_scroll();
458 return false;
458 return false;
459 }
459 }
460 },
460 },
461 's' : {
461 's' : {
462 help : 'save notebook',
462 help : 'save notebook',
463 help_index : 'fa',
463 help_index : 'fa',
464 handler : function (event) {
464 handler : function (event) {
465 IPython.notebook.save_checkpoint();
465 IPython.notebook.save_checkpoint();
466 return false;
466 return false;
467 }
467 }
468 },
468 },
469 'ctrl+j' : {
469 'ctrl+j' : {
470 help : 'move cell down',
470 help : 'move cell down',
471 help_index : 'eb',
471 help_index : 'eb',
472 handler : function (event) {
472 handler : function (event) {
473 IPython.notebook.move_cell_down();
473 IPython.notebook.move_cell_down();
474 return false;
474 return false;
475 }
475 }
476 },
476 },
477 'ctrl+k' : {
477 'ctrl+k' : {
478 help : 'move cell up',
478 help : 'move cell up',
479 help_index : 'ea',
479 help_index : 'ea',
480 handler : function (event) {
480 handler : function (event) {
481 IPython.notebook.move_cell_up();
481 IPython.notebook.move_cell_up();
482 return false;
482 return false;
483 }
483 }
484 },
484 },
485 'l' : {
485 'l' : {
486 help : 'toggle line numbers',
486 help : 'toggle line numbers',
487 help_index : 'ga',
487 help_index : 'ga',
488 handler : function (event) {
488 handler : function (event) {
489 IPython.notebook.cell_toggle_line_numbers();
489 IPython.notebook.cell_toggle_line_numbers();
490 return false;
490 return false;
491 }
491 }
492 },
492 },
493 'i' : {
493 'i' : {
494 help : 'interrupt kernel (press twice)',
494 help : 'interrupt kernel (press twice)',
495 help_index : 'ha',
495 help_index : 'ha',
496 count: 2,
496 count: 2,
497 handler : function (event) {
497 handler : function (event) {
498 IPython.notebook.kernel.interrupt();
498 IPython.notebook.kernel.interrupt();
499 return false;
499 return false;
500 }
500 }
501 },
501 },
502 '0' : {
502 '0' : {
503 help : 'restart kernel (press twice)',
503 help : 'restart kernel (press twice)',
504 help_index : 'hb',
504 help_index : 'hb',
505 count: 2,
505 count: 2,
506 handler : function (event) {
506 handler : function (event) {
507 IPython.notebook.restart_kernel();
507 IPython.notebook.restart_kernel();
508 return false;
508 return false;
509 }
509 }
510 },
510 },
511 'h' : {
511 'h' : {
512 help : 'keyboard shortcuts',
512 help : 'keyboard shortcuts',
513 help_index : 'gd',
513 help_index : 'ge',
514 handler : function (event) {
514 handler : function (event) {
515 IPython.quick_help.show_keyboard_shortcuts();
515 IPython.quick_help.show_keyboard_shortcuts();
516 return false;
516 return false;
517 }
517 }
518 },
518 },
519 'z' : {
519 'z' : {
520 help : 'undo last delete',
520 help : 'undo last delete',
521 help_index : 'ei',
521 help_index : 'ei',
522 handler : function (event) {
522 handler : function (event) {
523 IPython.notebook.undelete_cell();
523 IPython.notebook.undelete_cell();
524 return false;
524 return false;
525 }
525 }
526 },
526 },
527 'shift+m' : {
527 'shift+m' : {
528 help : 'merge cell below',
528 help : 'merge cell below',
529 help_index : 'ek',
529 help_index : 'ek',
530 handler : function (event) {
530 handler : function (event) {
531 IPython.notebook.merge_cell_below();
531 IPython.notebook.merge_cell_below();
532 return false;
532 return false;
533 }
533 }
534 },
534 },
535 'q' : {
536 help : 'close pager',
537 help_index : 'gd',
538 handler : function (event) {
539 IPython.pager.collapse();
540 return false;
541 }
542 },
535 }
543 }
536
544
537
545
538 // Shortcut manager class
546 // Shortcut manager class
539
547
540 var ShortcutManager = function (delay) {
548 var ShortcutManager = function (delay) {
541 this._shortcuts = {}
549 this._shortcuts = {}
542 this._counts = {}
550 this._counts = {}
543 this._timers = {}
551 this._timers = {}
544 this.delay = delay || 800; // delay in milliseconds
552 this.delay = delay || 800; // delay in milliseconds
545 }
553 }
546
554
547 ShortcutManager.prototype.help = function () {
555 ShortcutManager.prototype.help = function () {
548 var help = [];
556 var help = [];
549 for (var shortcut in this._shortcuts) {
557 for (var shortcut in this._shortcuts) {
550 var help_string = this._shortcuts[shortcut]['help'];
558 var help_string = this._shortcuts[shortcut]['help'];
551 var help_index = this._shortcuts[shortcut]['help_index'];
559 var help_index = this._shortcuts[shortcut]['help_index'];
552 if (help_string) {
560 if (help_string) {
553 if (platform === 'MacOS') {
561 if (platform === 'MacOS') {
554 shortcut = shortcut.replace('meta', 'cmd');
562 shortcut = shortcut.replace('meta', 'cmd');
555 }
563 }
556 help.push({
564 help.push({
557 shortcut: shortcut,
565 shortcut: shortcut,
558 help: help_string,
566 help: help_string,
559 help_index: help_index}
567 help_index: help_index}
560 );
568 );
561 }
569 }
562 }
570 }
563 help.sort(function (a, b) {
571 help.sort(function (a, b) {
564 if (a.help_index > b.help_index)
572 if (a.help_index > b.help_index)
565 return 1;
573 return 1;
566 if (a.help_index < b.help_index)
574 if (a.help_index < b.help_index)
567 return -1;
575 return -1;
568 return 0;
576 return 0;
569 });
577 });
570 return help;
578 return help;
571 }
579 }
572
580
573 ShortcutManager.prototype.normalize_key = function (key) {
581 ShortcutManager.prototype.normalize_key = function (key) {
574 return inv_keycodes[keycodes[key]];
582 return inv_keycodes[keycodes[key]];
575 }
583 }
576
584
577 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
585 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
578 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
586 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
579 shortcut = shortcut.replace('cmd', 'meta').toLowerCase();
587 shortcut = shortcut.replace('cmd', 'meta').toLowerCase();
580 var values = shortcut.split("+");
588 var values = shortcut.split("+");
581 if (values.length === 1) {
589 if (values.length === 1) {
582 return this.normalize_key(values[0])
590 return this.normalize_key(values[0])
583 } else {
591 } else {
584 var modifiers = values.slice(0,-1);
592 var modifiers = values.slice(0,-1);
585 var key = this.normalize_key(values[values.length-1]);
593 var key = this.normalize_key(values[values.length-1]);
586 modifiers.sort();
594 modifiers.sort();
587 return modifiers.join('+') + '+' + key;
595 return modifiers.join('+') + '+' + key;
588 }
596 }
589 }
597 }
590
598
591 ShortcutManager.prototype.event_to_shortcut = function (event) {
599 ShortcutManager.prototype.event_to_shortcut = function (event) {
592 // Convert a jQuery keyboard event to a strong based keyboard shortcut
600 // Convert a jQuery keyboard event to a strong based keyboard shortcut
593 var shortcut = '';
601 var shortcut = '';
594 var key = inv_keycodes[event.which]
602 var key = inv_keycodes[event.which]
595 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
603 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
596 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
604 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
597 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
605 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
598 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
606 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
599 shortcut += key;
607 shortcut += key;
600 return shortcut
608 return shortcut
601 }
609 }
602
610
603 ShortcutManager.prototype.clear_shortcuts = function () {
611 ShortcutManager.prototype.clear_shortcuts = function () {
604 this._shortcuts = {};
612 this._shortcuts = {};
605 }
613 }
606
614
607 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
615 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
608 if (typeof(data) === 'function') {
616 if (typeof(data) === 'function') {
609 data = {help: '', help_index: '', handler: data}
617 data = {help: '', help_index: '', handler: data}
610 }
618 }
611 data.help_index = data.help_index || '';
619 data.help_index = data.help_index || '';
612 data.help = data.help || '';
620 data.help = data.help || '';
613 data.count = data.count || 1;
621 data.count = data.count || 1;
614 if (data.help_index === '') {
622 if (data.help_index === '') {
615 data.help_index = 'zz';
623 data.help_index = 'zz';
616 }
624 }
617 shortcut = this.normalize_shortcut(shortcut);
625 shortcut = this.normalize_shortcut(shortcut);
618 this._counts[shortcut] = 0;
626 this._counts[shortcut] = 0;
619 this._shortcuts[shortcut] = data;
627 this._shortcuts[shortcut] = data;
620 }
628 }
621
629
622 ShortcutManager.prototype.add_shortcuts = function (data) {
630 ShortcutManager.prototype.add_shortcuts = function (data) {
623 for (var shortcut in data) {
631 for (var shortcut in data) {
624 this.add_shortcut(shortcut, data[shortcut]);
632 this.add_shortcut(shortcut, data[shortcut]);
625 }
633 }
626 }
634 }
627
635
628 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
636 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
629 shortcut = this.normalize_shortcut(shortcut);
637 shortcut = this.normalize_shortcut(shortcut);
630 delete this._counts[shortcut];
638 delete this._counts[shortcut];
631 delete this._shortcuts[shortcut];
639 delete this._shortcuts[shortcut];
632 }
640 }
633
641
634 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
642 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
635 var that = this;
643 var that = this;
636 var c = this._counts;
644 var c = this._counts;
637 var t = this._timers;
645 var t = this._timers;
638 var timer = null;
646 var timer = null;
639 if (c[shortcut] === data.count-1) {
647 if (c[shortcut] === data.count-1) {
640 c[shortcut] = 0;
648 c[shortcut] = 0;
641 var timer = t[shortcut];
649 var timer = t[shortcut];
642 if (timer) {clearTimeout(timer); delete t[shortcut];}
650 if (timer) {clearTimeout(timer); delete t[shortcut];}
643 return data.handler(event);
651 return data.handler(event);
644 } else {
652 } else {
645 c[shortcut] = c[shortcut] + 1;
653 c[shortcut] = c[shortcut] + 1;
646 timer = setTimeout(function () {
654 timer = setTimeout(function () {
647 c[shortcut] = 0;
655 c[shortcut] = 0;
648 }, that.delay);
656 }, that.delay);
649 t[shortcut] = timer;
657 t[shortcut] = timer;
650 }
658 }
651 return false;
659 return false;
652 }
660 }
653
661
654 ShortcutManager.prototype.call_handler = function (event) {
662 ShortcutManager.prototype.call_handler = function (event) {
655 var shortcut = this.event_to_shortcut(event);
663 var shortcut = this.event_to_shortcut(event);
656 var data = this._shortcuts[shortcut];
664 var data = this._shortcuts[shortcut];
657 if (data) {
665 if (data) {
658 var handler = data['handler'];
666 var handler = data['handler'];
659 if (handler) {
667 if (handler) {
660 if (data.count === 1) {
668 if (data.count === 1) {
661 return handler(event);
669 return handler(event);
662 } else if (data.count > 1) {
670 } else if (data.count > 1) {
663 return this.count_handler(shortcut, event, data);
671 return this.count_handler(shortcut, event, data);
664 }
672 }
665 }
673 }
666 }
674 }
667 return true;
675 return true;
668 }
676 }
669
677
670
678
671
679
672 // Main keyboard manager for the notebook
680 // Main keyboard manager for the notebook
673
681
674 var KeyboardManager = function () {
682 var KeyboardManager = function () {
675 this.mode = 'command';
683 this.mode = 'command';
676 this.enabled = true;
684 this.enabled = true;
677 this.bind_events();
685 this.bind_events();
678 this.command_shortcuts = new ShortcutManager();
686 this.command_shortcuts = new ShortcutManager();
679 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
687 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
680 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
688 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
681 this.edit_shortcuts = new ShortcutManager();
689 this.edit_shortcuts = new ShortcutManager();
682 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
690 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
683 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
691 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
684 };
692 };
685
693
686 KeyboardManager.prototype.bind_events = function () {
694 KeyboardManager.prototype.bind_events = function () {
687 var that = this;
695 var that = this;
688 $(document).keydown(function (event) {
696 $(document).keydown(function (event) {
689 return that.handle_keydown(event);
697 return that.handle_keydown(event);
690 });
698 });
691 };
699 };
692
700
693 KeyboardManager.prototype.handle_keydown = function (event) {
701 KeyboardManager.prototype.handle_keydown = function (event) {
694 var notebook = IPython.notebook;
702 var notebook = IPython.notebook;
695
703
696 if (event.which === keycodes['esc']) {
704 if (event.which === keycodes['esc']) {
697 // Intercept escape at highest level to avoid closing
705 // Intercept escape at highest level to avoid closing
698 // websocket connection with firefox
706 // websocket connection with firefox
699 event.preventDefault();
707 event.preventDefault();
700 }
708 }
701
709
702 if (!this.enabled) {
710 if (!this.enabled) {
703 if (event.which === keycodes['esc']) {
711 if (event.which === keycodes['esc']) {
704 // ESC
712 // ESC
705 notebook.command_mode();
713 notebook.command_mode();
706 return false;
714 return false;
707 }
715 }
708 return true;
716 return true;
709 }
717 }
710
718
711 if (this.mode === 'edit') {
719 if (this.mode === 'edit') {
712 return this.edit_shortcuts.call_handler(event);
720 return this.edit_shortcuts.call_handler(event);
713 } else if (this.mode === 'command') {
721 } else if (this.mode === 'command') {
714 return this.command_shortcuts.call_handler(event);
722 return this.command_shortcuts.call_handler(event);
715 }
723 }
716 return true;
724 return true;
717 }
725 }
718
726
719 KeyboardManager.prototype.edit_mode = function () {
727 KeyboardManager.prototype.edit_mode = function () {
720 this.last_mode = this.mode;
728 this.last_mode = this.mode;
721 this.mode = 'edit';
729 this.mode = 'edit';
722 }
730 }
723
731
724 KeyboardManager.prototype.command_mode = function () {
732 KeyboardManager.prototype.command_mode = function () {
725 this.last_mode = this.mode;
733 this.last_mode = this.mode;
726 this.mode = 'command';
734 this.mode = 'command';
727 }
735 }
728
736
729 KeyboardManager.prototype.enable = function () {
737 KeyboardManager.prototype.enable = function () {
730 this.enabled = true;
738 this.enabled = true;
731 }
739 }
732
740
733 KeyboardManager.prototype.disable = function () {
741 KeyboardManager.prototype.disable = function () {
734 this.enabled = false;
742 this.enabled = false;
735 }
743 }
736
744
737 KeyboardManager.prototype.register_events = function (e) {
745 KeyboardManager.prototype.register_events = function (e) {
738 var that = this;
746 var that = this;
739 e.on('focusin', function () {
747 e.on('focusin', function () {
740 that.disable();
748 that.disable();
741 });
749 });
742 e.on('focusout', function () {
750 e.on('focusout', function () {
743 that.enable();
751 that.enable();
744 });
752 });
745 // There are times (raw_input) where we remove the element from the DOM before
753 // There are times (raw_input) where we remove the element from the DOM before
746 // focusout is called. In this case we bind to the remove event of jQueryUI,
754 // focusout is called. In this case we bind to the remove event of jQueryUI,
747 // which gets triggered upon removal.
755 // which gets triggered upon removal.
748 e.on('remove', function () {
756 e.on('remove', function () {
749 that.enable();
757 that.enable();
750 });
758 });
751 }
759 }
752
760
753
761
754 IPython.keycodes = keycodes;
762 IPython.keycodes = keycodes;
755 IPython.inv_keycodes = inv_keycodes;
763 IPython.inv_keycodes = inv_keycodes;
756 IPython.default_common_shortcuts = default_common_shortcuts;
764 IPython.default_common_shortcuts = default_common_shortcuts;
757 IPython.default_edit_shortcuts = default_edit_shortcuts;
765 IPython.default_edit_shortcuts = default_edit_shortcuts;
758 IPython.default_command_shortcuts = default_command_shortcuts;
766 IPython.default_command_shortcuts = default_command_shortcuts;
759 IPython.ShortcutManager = ShortcutManager;
767 IPython.ShortcutManager = ShortcutManager;
760 IPython.KeyboardManager = KeyboardManager;
768 IPython.KeyboardManager = KeyboardManager;
761
769
762 return IPython;
770 return IPython;
763
771
764 }(IPython));
772 }(IPython));
@@ -1,128 +1,122 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // On document ready
9 // On document ready
10 //============================================================================
10 //============================================================================
11 "use strict";
12
11
13 // for the time beeing, we have to pass marked as a parameter here,
12 // for the time beeing, we have to pass marked as a parameter here,
14 // as injecting require.js make marked not to put itself in the globals,
13 // as injecting require.js make marked not to put itself in the globals,
15 // which make both this file fail at setting marked configuration, and textcell.js
14 // which make both this file fail at setting marked configuration, and textcell.js
16 // which search marked into global.
15 // which search marked into global.
17 require(['components/marked/lib/marked',
16 require(['components/marked/lib/marked',
18 'notebook/js/widgets/init'],
17 'notebook/js/widgets/init'],
19
18
20 function (marked) {
19 function (marked) {
20 "use strict";
21
21
22 window.marked = marked
22 window.marked = marked;
23
23
24 // monkey patch CM to be able to syntax highlight cell magics
24 // monkey patch CM to be able to syntax highlight cell magics
25 // bug reported upstream,
25 // bug reported upstream,
26 // see https://github.com/marijnh/CodeMirror2/issues/670
26 // see https://github.com/marijnh/CodeMirror2/issues/670
27 if(CodeMirror.getMode(1,'text/plain').indent == undefined ){
27 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
28 console.log('patching CM for undefined indent');
28 console.log('patching CM for undefined indent');
29 CodeMirror.modes.null = function() {
29 CodeMirror.modes.null = function() {
30 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0}}
30 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
31 }
31 };
32 }
32 }
33
33
34 CodeMirror.patchedGetMode = function(config, mode){
34 CodeMirror.patchedGetMode = function(config, mode){
35 var cmmode = CodeMirror.getMode(config, mode);
35 var cmmode = CodeMirror.getMode(config, mode);
36 if(cmmode.indent == null)
36 if(cmmode.indent === null) {
37 {
38 console.log('patch mode "' , mode, '" on the fly');
37 console.log('patch mode "' , mode, '" on the fly');
39 cmmode.indent = function(){return 0};
38 cmmode.indent = function(){return 0;};
40 }
39 }
41 return cmmode;
40 return cmmode;
42 }
41 };
43 // end monkey patching CodeMirror
42 // end monkey patching CodeMirror
44
43
45 IPython.mathjaxutils.init();
44 IPython.mathjaxutils.init();
46
45
47 $('#ipython-main-app').addClass('border-box-sizing');
46 $('#ipython-main-app').addClass('border-box-sizing');
48 $('div#notebook_panel').addClass('border-box-sizing');
47 $('div#notebook_panel').addClass('border-box-sizing');
49
48
50 var baseProjectUrl = $('body').data('baseProjectUrl');
49 var opts = {
51 var notebookPath = $('body').data('notebookPath');
50 base_url : IPython.utils.get_body_data("baseUrl"),
52 var notebookName = $('body').data('notebookName');
51 base_kernel_url : IPython.utils.get_body_data("baseKernelUrl"),
53 notebookName = decodeURIComponent(notebookName);
52 notebook_path : IPython.utils.get_body_data("notebookPath"),
54 notebookPath = decodeURIComponent(notebookPath);
53 notebook_name : IPython.utils.get_body_data('notebookName')
55 console.log(notebookName);
54 };
56 if (notebookPath == 'None'){
57 notebookPath = "";
58 }
59
55
60 IPython.page = new IPython.Page();
56 IPython.page = new IPython.Page();
61 IPython.layout_manager = new IPython.LayoutManager();
57 IPython.layout_manager = new IPython.LayoutManager();
62 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
58 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
63 IPython.quick_help = new IPython.QuickHelp();
59 IPython.quick_help = new IPython.QuickHelp();
64 IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl});
60 IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts);
65 IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, notebookPath:notebookPath, notebookName:notebookName});
61 IPython.notebook = new IPython.Notebook('div#notebook', opts);
66 IPython.keyboard_manager = new IPython.KeyboardManager();
62 IPython.keyboard_manager = new IPython.KeyboardManager();
67 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
63 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
68 IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl, notebookPath: notebookPath})
64 IPython.menubar = new IPython.MenuBar('#menubar', opts);
69 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container')
65 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container');
70 IPython.tooltip = new IPython.Tooltip()
66 IPython.tooltip = new IPython.Tooltip();
71 IPython.notification_area = new IPython.NotificationArea('#notification_area')
67 IPython.notification_area = new IPython.NotificationArea('#notification_area');
72 IPython.notification_area.init_notification_widgets();
68 IPython.notification_area.init_notification_widgets();
73
69
74 IPython.layout_manager.do_resize();
70 IPython.layout_manager.do_resize();
75
71
76 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
72 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
77 '<span id="test2" style="font-weight: bold;">x</span>'+
73 '<span id="test2" style="font-weight: bold;">x</span>'+
78 '<span id="test3" style="font-style: italic;">x</span></pre></div>')
74 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
79 var nh = $('#test1').innerHeight();
75 var nh = $('#test1').innerHeight();
80 var bh = $('#test2').innerHeight();
76 var bh = $('#test2').innerHeight();
81 var ih = $('#test3').innerHeight();
77 var ih = $('#test3').innerHeight();
82 if(nh != bh || nh != ih) {
78 if(nh != bh || nh != ih) {
83 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
79 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
84 }
80 }
85 $('#fonttest').remove();
81 $('#fonttest').remove();
86
82
87 IPython.page.show();
83 IPython.page.show();
88
84
89 IPython.layout_manager.do_resize();
85 IPython.layout_manager.do_resize();
90 var first_load = function () {
86 var first_load = function () {
91 IPython.layout_manager.do_resize();
87 IPython.layout_manager.do_resize();
92 var hash = document.location.hash;
88 var hash = document.location.hash;
93 if (hash) {
89 if (hash) {
94 document.location.hash = '';
90 document.location.hash = '';
95 document.location.hash = hash;
91 document.location.hash = hash;
96 }
92 }
97 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
93 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
98 // only do this once
94 // only do this once
99 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
95 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
100 };
96 };
101
97
102 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
98 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
103 $([IPython.events]).trigger('app_initialized.NotebookApp');
99 $([IPython.events]).trigger('app_initialized.NotebookApp');
104 IPython.notebook.load_notebook(notebookName, notebookPath);
100 IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path);
105
101
106 if (marked) {
102 if (marked) {
107 marked.setOptions({
103 marked.setOptions({
108 gfm : true,
104 gfm : true,
109 tables: true,
105 tables: true,
110 langPrefix: "language-",
106 langPrefix: "language-",
111 highlight: function(code, lang) {
107 highlight: function(code, lang) {
112 if (!lang) {
108 if (!lang) {
113 // no language, no highlight
109 // no language, no highlight
114 return code;
110 return code;
115 }
111 }
116 var highlighted;
112 var highlighted;
117 try {
113 try {
118 highlighted = hljs.highlight(lang, code, false);
114 highlighted = hljs.highlight(lang, code, false);
119 } catch(err) {
115 } catch(err) {
120 highlighted = hljs.highlightAuto(code);
116 highlighted = hljs.highlightAuto(code);
121 }
117 }
122 return highlighted.value;
118 return highlighted.value;
123 }
119 }
124 })
120 });
125 }
121 }
126 }
122 });
127
128 );
@@ -1,213 +1,214 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // ToolBar
9 // ToolBar
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var MainToolBar = function (selector) {
15 var MainToolBar = function (selector) {
16 IPython.ToolBar.apply(this, arguments);
16 IPython.ToolBar.apply(this, arguments);
17 this.construct();
17 this.construct();
18 this.add_celltype_list();
18 this.add_celltype_list();
19 this.add_celltoolbar_list();
19 this.add_celltoolbar_list();
20 this.bind_events();
20 this.bind_events();
21 };
21 };
22
22
23 MainToolBar.prototype = new IPython.ToolBar();
23 MainToolBar.prototype = new IPython.ToolBar();
24
24
25 MainToolBar.prototype.construct = function () {
25 MainToolBar.prototype.construct = function () {
26 this.add_buttons_group([
26 this.add_buttons_group([
27 {
27 {
28 id : 'save_b',
28 id : 'save_b',
29 label : 'Save and Checkpoint',
29 label : 'Save and Checkpoint',
30 icon : 'icon-save',
30 icon : 'icon-save',
31 callback : function () {
31 callback : function () {
32 IPython.notebook.save_checkpoint();
32 IPython.notebook.save_checkpoint();
33 }
33 }
34 }
34 }
35 ]);
35 ]);
36
36
37 this.add_buttons_group([
37 this.add_buttons_group([
38 {
38 {
39 id : 'insert_below_b',
39 id : 'insert_below_b',
40 label : 'Insert Cell Below',
40 label : 'Insert Cell Below',
41 icon : 'icon-plus-sign',
41 icon : 'icon-plus-sign',
42 callback : function () {
42 callback : function () {
43 IPython.notebook.insert_cell_below('code');
43 IPython.notebook.insert_cell_below('code');
44 IPython.notebook.select_next();
44 IPython.notebook.select_next();
45 IPython.notebook.focus_cell();
45 IPython.notebook.focus_cell();
46 }
46 }
47 }
47 }
48 ],'insert_above_below');
48 ],'insert_above_below');
49
49
50 this.add_buttons_group([
50 this.add_buttons_group([
51 {
51 {
52 id : 'cut_b',
52 id : 'cut_b',
53 label : 'Cut Cell',
53 label : 'Cut Cell',
54 icon : 'icon-cut',
54 icon : 'icon-cut',
55 callback : function () {
55 callback : function () {
56 IPython.notebook.cut_cell();
56 IPython.notebook.cut_cell();
57 }
57 }
58 },
58 },
59 {
59 {
60 id : 'copy_b',
60 id : 'copy_b',
61 label : 'Copy Cell',
61 label : 'Copy Cell',
62 icon : 'icon-copy',
62 icon : 'icon-copy',
63 callback : function () {
63 callback : function () {
64 IPython.notebook.copy_cell();
64 IPython.notebook.copy_cell();
65 }
65 }
66 },
66 },
67 {
67 {
68 id : 'paste_b',
68 id : 'paste_b',
69 label : 'Paste Cell Below',
69 label : 'Paste Cell Below',
70 icon : 'icon-paste',
70 icon : 'icon-paste',
71 callback : function () {
71 callback : function () {
72 IPython.notebook.paste_cell_below();
72 IPython.notebook.paste_cell_below();
73 }
73 }
74 }
74 }
75 ],'cut_copy_paste');
75 ],'cut_copy_paste');
76
76
77 this.add_buttons_group([
77 this.add_buttons_group([
78 {
78 {
79 id : 'move_up_b',
79 id : 'move_up_b',
80 label : 'Move Cell Up',
80 label : 'Move Cell Up',
81 icon : 'icon-arrow-up',
81 icon : 'icon-arrow-up',
82 callback : function () {
82 callback : function () {
83 IPython.notebook.move_cell_up();
83 IPython.notebook.move_cell_up();
84 }
84 }
85 },
85 },
86 {
86 {
87 id : 'move_down_b',
87 id : 'move_down_b',
88 label : 'Move Cell Down',
88 label : 'Move Cell Down',
89 icon : 'icon-arrow-down',
89 icon : 'icon-arrow-down',
90 callback : function () {
90 callback : function () {
91 IPython.notebook.move_cell_down();
91 IPython.notebook.move_cell_down();
92 }
92 }
93 }
93 }
94 ],'move_up_down');
94 ],'move_up_down');
95
95
96
96
97 this.add_buttons_group([
97 this.add_buttons_group([
98 {
98 {
99 id : 'run_b',
99 id : 'run_b',
100 label : 'Run Cell',
100 label : 'Run Cell',
101 icon : 'icon-play',
101 icon : 'icon-play',
102 callback : function () {
102 callback : function () {
103 IPython.notebook.execute_cell();
103 // emulate default shift-enter behavior
104 }
104 IPython.notebook.execute_cell_and_select_below();
105 }
105 },
106 },
106 {
107 {
107 id : 'interrupt_b',
108 id : 'interrupt_b',
108 label : 'Interrupt',
109 label : 'Interrupt',
109 icon : 'icon-stop',
110 icon : 'icon-stop',
110 callback : function () {
111 callback : function () {
111 IPython.notebook.session.interrupt_kernel();
112 IPython.notebook.session.interrupt_kernel();
112 }
113 }
113 },
114 },
114 {
115 {
115 id : 'repeat_b',
116 id : 'repeat_b',
116 label : 'Restart Kernel',
117 label : 'Restart Kernel',
117 icon : 'icon-repeat',
118 icon : 'icon-repeat',
118 callback : function () {
119 callback : function () {
119 IPython.notebook.restart_kernel();
120 IPython.notebook.restart_kernel();
120 }
121 }
121 }
122 }
122 ],'run_int');
123 ],'run_int');
123 };
124 };
124
125
125 MainToolBar.prototype.add_celltype_list = function () {
126 MainToolBar.prototype.add_celltype_list = function () {
126 this.element
127 this.element
127 .append($('<select/>')
128 .append($('<select/>')
128 .attr('id','cell_type')
129 .attr('id','cell_type')
129 // .addClass('ui-widget-content')
130 // .addClass('ui-widget-content')
130 .append($('<option/>').attr('value','code').text('Code'))
131 .append($('<option/>').attr('value','code').text('Code'))
131 .append($('<option/>').attr('value','markdown').text('Markdown'))
132 .append($('<option/>').attr('value','markdown').text('Markdown'))
132 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
133 .append($('<option/>').attr('value','raw').text('Raw NBConvert'))
133 .append($('<option/>').attr('value','heading1').text('Heading 1'))
134 .append($('<option/>').attr('value','heading1').text('Heading 1'))
134 .append($('<option/>').attr('value','heading2').text('Heading 2'))
135 .append($('<option/>').attr('value','heading2').text('Heading 2'))
135 .append($('<option/>').attr('value','heading3').text('Heading 3'))
136 .append($('<option/>').attr('value','heading3').text('Heading 3'))
136 .append($('<option/>').attr('value','heading4').text('Heading 4'))
137 .append($('<option/>').attr('value','heading4').text('Heading 4'))
137 .append($('<option/>').attr('value','heading5').text('Heading 5'))
138 .append($('<option/>').attr('value','heading5').text('Heading 5'))
138 .append($('<option/>').attr('value','heading6').text('Heading 6'))
139 .append($('<option/>').attr('value','heading6').text('Heading 6'))
139 );
140 );
140 };
141 };
141
142
142
143
143 MainToolBar.prototype.add_celltoolbar_list = function () {
144 MainToolBar.prototype.add_celltoolbar_list = function () {
144 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
145 var label = $('<span/>').addClass("navbar-text").text('Cell Toolbar:');
145 var select = $('<select/>')
146 var select = $('<select/>')
146 // .addClass('ui-widget-content')
147 // .addClass('ui-widget-content')
147 .attr('id', 'ctb_select')
148 .attr('id', 'ctb_select')
148 .append($('<option/>').attr('value', '').text('None'));
149 .append($('<option/>').attr('value', '').text('None'));
149 this.element.append(label).append(select);
150 this.element.append(label).append(select);
150 select.change(function() {
151 select.change(function() {
151 var val = $(this).val()
152 var val = $(this).val()
152 if (val =='') {
153 if (val =='') {
153 IPython.CellToolbar.global_hide();
154 IPython.CellToolbar.global_hide();
154 delete IPython.notebook.metadata.celltoolbar;
155 delete IPython.notebook.metadata.celltoolbar;
155 } else {
156 } else {
156 IPython.CellToolbar.global_show();
157 IPython.CellToolbar.global_show();
157 IPython.CellToolbar.activate_preset(val);
158 IPython.CellToolbar.activate_preset(val);
158 IPython.notebook.metadata.celltoolbar = val;
159 IPython.notebook.metadata.celltoolbar = val;
159 }
160 }
160 });
161 });
161 // Setup the currently registered presets.
162 // Setup the currently registered presets.
162 var presets = IPython.CellToolbar.list_presets();
163 var presets = IPython.CellToolbar.list_presets();
163 for (var i=0; i<presets.length; i++) {
164 for (var i=0; i<presets.length; i++) {
164 var name = presets[i];
165 var name = presets[i];
165 select.append($('<option/>').attr('value', name).text(name));
166 select.append($('<option/>').attr('value', name).text(name));
166 }
167 }
167 // Setup future preset registrations.
168 // Setup future preset registrations.
168 $([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
169 $([IPython.events]).on('preset_added.CellToolbar', function (event, data) {
169 var name = data.name;
170 var name = data.name;
170 select.append($('<option/>').attr('value', name).text(name));
171 select.append($('<option/>').attr('value', name).text(name));
171 });
172 });
172 };
173 };
173
174
174
175
175 MainToolBar.prototype.bind_events = function () {
176 MainToolBar.prototype.bind_events = function () {
176 var that = this;
177 var that = this;
177
178
178 this.element.find('#cell_type').change(function () {
179 this.element.find('#cell_type').change(function () {
179 var cell_type = $(this).val();
180 var cell_type = $(this).val();
180 if (cell_type === 'code') {
181 if (cell_type === 'code') {
181 IPython.notebook.to_code();
182 IPython.notebook.to_code();
182 } else if (cell_type === 'markdown') {
183 } else if (cell_type === 'markdown') {
183 IPython.notebook.to_markdown();
184 IPython.notebook.to_markdown();
184 } else if (cell_type === 'raw') {
185 } else if (cell_type === 'raw') {
185 IPython.notebook.to_raw();
186 IPython.notebook.to_raw();
186 } else if (cell_type === 'heading1') {
187 } else if (cell_type === 'heading1') {
187 IPython.notebook.to_heading(undefined, 1);
188 IPython.notebook.to_heading(undefined, 1);
188 } else if (cell_type === 'heading2') {
189 } else if (cell_type === 'heading2') {
189 IPython.notebook.to_heading(undefined, 2);
190 IPython.notebook.to_heading(undefined, 2);
190 } else if (cell_type === 'heading3') {
191 } else if (cell_type === 'heading3') {
191 IPython.notebook.to_heading(undefined, 3);
192 IPython.notebook.to_heading(undefined, 3);
192 } else if (cell_type === 'heading4') {
193 } else if (cell_type === 'heading4') {
193 IPython.notebook.to_heading(undefined, 4);
194 IPython.notebook.to_heading(undefined, 4);
194 } else if (cell_type === 'heading5') {
195 } else if (cell_type === 'heading5') {
195 IPython.notebook.to_heading(undefined, 5);
196 IPython.notebook.to_heading(undefined, 5);
196 } else if (cell_type === 'heading6') {
197 } else if (cell_type === 'heading6') {
197 IPython.notebook.to_heading(undefined, 6);
198 IPython.notebook.to_heading(undefined, 6);
198 }
199 }
199 });
200 });
200 $([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
201 $([IPython.events]).on('selected_cell_type_changed.Notebook', function (event, data) {
201 if (data.cell_type === 'heading') {
202 if (data.cell_type === 'heading') {
202 that.element.find('#cell_type').val(data.cell_type+data.level);
203 that.element.find('#cell_type').val(data.cell_type+data.level);
203 } else {
204 } else {
204 that.element.find('#cell_type').val(data.cell_type);
205 that.element.find('#cell_type').val(data.cell_type);
205 }
206 }
206 });
207 });
207 };
208 };
208
209
209 IPython.MainToolBar = MainToolBar;
210 IPython.MainToolBar = MainToolBar;
210
211
211 return IPython;
212 return IPython;
212
213
213 }(IPython));
214 }(IPython));
@@ -1,327 +1,318 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // MenuBar
9 // MenuBar
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule MenuBar
15 * @submodule MenuBar
16 */
16 */
17
17
18
18
19 var IPython = (function (IPython) {
19 var IPython = (function (IPython) {
20 "use strict";
20 "use strict";
21
21
22 var utils = IPython.utils;
22 var utils = IPython.utils;
23
23
24 /**
24 /**
25 * A MenuBar Class to generate the menubar of IPython notebook
25 * A MenuBar Class to generate the menubar of IPython notebook
26 * @Class MenuBar
26 * @Class MenuBar
27 *
27 *
28 * @constructor
28 * @constructor
29 *
29 *
30 *
30 *
31 * @param selector {string} selector for the menubar element in DOM
31 * @param selector {string} selector for the menubar element in DOM
32 * @param {object} [options]
32 * @param {object} [options]
33 * @param [options.baseProjectUrl] {String} String to use for the
33 * @param [options.base_url] {String} String to use for the
34 * Base Project url, default would be to inspect
34 * base project url. Default is to inspect
35 * $('body').data('baseProjectUrl');
35 * $('body').data('baseUrl');
36 * does not support change for now is set through this option
36 * does not support change for now is set through this option
37 */
37 */
38 var MenuBar = function (selector, options) {
38 var MenuBar = function (selector, options) {
39 options = options || {};
39 options = options || {};
40 if (options.baseProjectUrl !== undefined) {
40 this.base_url = options.base_url || IPython.utils.get_body_data("baseUrl");
41 this._baseProjectUrl = options.baseProjectUrl;
42 }
43 this.selector = selector;
41 this.selector = selector;
44 if (this.selector !== undefined) {
42 if (this.selector !== undefined) {
45 this.element = $(selector);
43 this.element = $(selector);
46 this.style();
44 this.style();
47 this.bind_events();
45 this.bind_events();
48 }
46 }
49 };
47 };
50
48
51 MenuBar.prototype.baseProjectUrl = function(){
52 return this._baseProjectUrl || $('body').data('baseProjectUrl');
53 };
54
55 MenuBar.prototype.notebookPath = function() {
56 var path = $('body').data('notebookPath');
57 path = decodeURIComponent(path);
58 return path;
59 };
60
61 MenuBar.prototype.style = function () {
49 MenuBar.prototype.style = function () {
62 this.element.addClass('border-box-sizing');
50 this.element.addClass('border-box-sizing');
63 this.element.find("li").click(function (event, ui) {
51 this.element.find("li").click(function (event, ui) {
64 // The selected cell loses focus when the menu is entered, so we
52 // The selected cell loses focus when the menu is entered, so we
65 // re-select it upon selection.
53 // re-select it upon selection.
66 var i = IPython.notebook.get_selected_index();
54 var i = IPython.notebook.get_selected_index();
67 IPython.notebook.select(i);
55 IPython.notebook.select(i);
68 }
56 }
69 );
57 );
70 };
58 };
71
59
72 MenuBar.prototype._nbconvert = function (format, download) {
60 MenuBar.prototype._nbconvert = function (format, download) {
73 download = download || false;
61 download = download || false;
74 var notebook_name = IPython.notebook.get_notebook_name();
62 var notebook_path = IPython.notebook.notebook_path;
63 var notebook_name = IPython.notebook.notebook_name;
75 if (IPython.notebook.dirty) {
64 if (IPython.notebook.dirty) {
76 IPython.notebook.save_notebook({async : false});
65 IPython.notebook.save_notebook({async : false});
77 }
66 }
78 var url = utils.url_path_join(
67 var url = utils.url_join_encode(
79 this.baseProjectUrl(),
68 this.base_url,
80 'nbconvert',
69 'nbconvert',
81 format,
70 format,
82 this.notebookPath(),
71 notebook_path,
83 notebook_name + '.ipynb'
72 notebook_name
84 ) + "?download=" + download.toString();
73 ) + "?download=" + download.toString();
85
74
86 window.open(url);
75 window.open(url);
87 }
76 };
88
77
89 MenuBar.prototype.bind_events = function () {
78 MenuBar.prototype.bind_events = function () {
90 // File
79 // File
91 var that = this;
80 var that = this;
92 this.element.find('#new_notebook').click(function () {
81 this.element.find('#new_notebook').click(function () {
93 IPython.notebook.new_notebook();
82 IPython.notebook.new_notebook();
94 });
83 });
95 this.element.find('#open_notebook').click(function () {
84 this.element.find('#open_notebook').click(function () {
96 window.open(utils.url_join_encode(
85 window.open(utils.url_join_encode(
97 that.baseProjectUrl(),
86 IPython.notebook.base_url,
98 'tree',
87 'tree',
99 that.notebookPath()
88 IPython.notebook.notebook_path
100 ));
89 ));
101 });
90 });
102 this.element.find('#copy_notebook').click(function () {
91 this.element.find('#copy_notebook').click(function () {
103 IPython.notebook.copy_notebook();
92 IPython.notebook.copy_notebook();
104 return false;
93 return false;
105 });
94 });
106 this.element.find('#download_ipynb').click(function () {
95 this.element.find('#download_ipynb').click(function () {
107 var notebook_name = IPython.notebook.get_notebook_name();
96 var base_url = IPython.notebook.base_url;
97 var notebook_path = IPython.notebook.notebook_path;
98 var notebook_name = IPython.notebook.notebook_name;
108 if (IPython.notebook.dirty) {
99 if (IPython.notebook.dirty) {
109 IPython.notebook.save_notebook({async : false});
100 IPython.notebook.save_notebook({async : false});
110 }
101 }
111
102
112 var url = utils.url_join_encode(
103 var url = utils.url_join_encode(
113 that.baseProjectUrl(),
104 base_url,
114 'files',
105 'files',
115 that.notebookPath(),
106 notebook_path,
116 notebook_name + '.ipynb'
107 notebook_name
117 );
108 );
118 window.location.assign(url);
109 window.location.assign(url);
119 });
110 });
120
111
121 this.element.find('#print_preview').click(function () {
112 this.element.find('#print_preview').click(function () {
122 that._nbconvert('html', false);
113 that._nbconvert('html', false);
123 });
114 });
124
115
125 this.element.find('#download_py').click(function () {
116 this.element.find('#download_py').click(function () {
126 that._nbconvert('python', true);
117 that._nbconvert('python', true);
127 });
118 });
128
119
129 this.element.find('#download_html').click(function () {
120 this.element.find('#download_html').click(function () {
130 that._nbconvert('html', true);
121 that._nbconvert('html', true);
131 });
122 });
132
123
133 this.element.find('#download_rst').click(function () {
124 this.element.find('#download_rst').click(function () {
134 that._nbconvert('rst', true);
125 that._nbconvert('rst', true);
135 });
126 });
136
127
137 this.element.find('#rename_notebook').click(function () {
128 this.element.find('#rename_notebook').click(function () {
138 IPython.save_widget.rename_notebook();
129 IPython.save_widget.rename_notebook();
139 });
130 });
140 this.element.find('#save_checkpoint').click(function () {
131 this.element.find('#save_checkpoint').click(function () {
141 IPython.notebook.save_checkpoint();
132 IPython.notebook.save_checkpoint();
142 });
133 });
143 this.element.find('#restore_checkpoint').click(function () {
134 this.element.find('#restore_checkpoint').click(function () {
144 });
135 });
145 this.element.find('#kill_and_exit').click(function () {
136 this.element.find('#kill_and_exit').click(function () {
146 IPython.notebook.session.delete();
137 IPython.notebook.session.delete();
147 setTimeout(function(){
138 setTimeout(function(){
148 // allow closing of new tabs in Chromium, impossible in FF
139 // allow closing of new tabs in Chromium, impossible in FF
149 window.open('', '_self', '');
140 window.open('', '_self', '');
150 window.close();
141 window.close();
151 }, 500);
142 }, 500);
152 });
143 });
153 // Edit
144 // Edit
154 this.element.find('#cut_cell').click(function () {
145 this.element.find('#cut_cell').click(function () {
155 IPython.notebook.cut_cell();
146 IPython.notebook.cut_cell();
156 });
147 });
157 this.element.find('#copy_cell').click(function () {
148 this.element.find('#copy_cell').click(function () {
158 IPython.notebook.copy_cell();
149 IPython.notebook.copy_cell();
159 });
150 });
160 this.element.find('#delete_cell').click(function () {
151 this.element.find('#delete_cell').click(function () {
161 IPython.notebook.delete_cell();
152 IPython.notebook.delete_cell();
162 });
153 });
163 this.element.find('#undelete_cell').click(function () {
154 this.element.find('#undelete_cell').click(function () {
164 IPython.notebook.undelete_cell();
155 IPython.notebook.undelete_cell();
165 });
156 });
166 this.element.find('#split_cell').click(function () {
157 this.element.find('#split_cell').click(function () {
167 IPython.notebook.split_cell();
158 IPython.notebook.split_cell();
168 });
159 });
169 this.element.find('#merge_cell_above').click(function () {
160 this.element.find('#merge_cell_above').click(function () {
170 IPython.notebook.merge_cell_above();
161 IPython.notebook.merge_cell_above();
171 });
162 });
172 this.element.find('#merge_cell_below').click(function () {
163 this.element.find('#merge_cell_below').click(function () {
173 IPython.notebook.merge_cell_below();
164 IPython.notebook.merge_cell_below();
174 });
165 });
175 this.element.find('#move_cell_up').click(function () {
166 this.element.find('#move_cell_up').click(function () {
176 IPython.notebook.move_cell_up();
167 IPython.notebook.move_cell_up();
177 });
168 });
178 this.element.find('#move_cell_down').click(function () {
169 this.element.find('#move_cell_down').click(function () {
179 IPython.notebook.move_cell_down();
170 IPython.notebook.move_cell_down();
180 });
171 });
181 this.element.find('#edit_nb_metadata').click(function () {
172 this.element.find('#edit_nb_metadata').click(function () {
182 IPython.notebook.edit_metadata();
173 IPython.notebook.edit_metadata();
183 });
174 });
184
175
185 // View
176 // View
186 this.element.find('#toggle_header').click(function () {
177 this.element.find('#toggle_header').click(function () {
187 $('div#header').toggle();
178 $('div#header').toggle();
188 IPython.layout_manager.do_resize();
179 IPython.layout_manager.do_resize();
189 });
180 });
190 this.element.find('#toggle_toolbar').click(function () {
181 this.element.find('#toggle_toolbar').click(function () {
191 $('div#maintoolbar').toggle();
182 $('div#maintoolbar').toggle();
192 IPython.layout_manager.do_resize();
183 IPython.layout_manager.do_resize();
193 });
184 });
194 // Insert
185 // Insert
195 this.element.find('#insert_cell_above').click(function () {
186 this.element.find('#insert_cell_above').click(function () {
196 IPython.notebook.insert_cell_above('code');
187 IPython.notebook.insert_cell_above('code');
197 IPython.notebook.select_prev();
188 IPython.notebook.select_prev();
198 });
189 });
199 this.element.find('#insert_cell_below').click(function () {
190 this.element.find('#insert_cell_below').click(function () {
200 IPython.notebook.insert_cell_below('code');
191 IPython.notebook.insert_cell_below('code');
201 IPython.notebook.select_next();
192 IPython.notebook.select_next();
202 });
193 });
203 // Cell
194 // Cell
204 this.element.find('#run_cell').click(function () {
195 this.element.find('#run_cell').click(function () {
205 IPython.notebook.execute_cell();
196 IPython.notebook.execute_cell();
206 });
197 });
207 this.element.find('#run_cell_select_below').click(function () {
198 this.element.find('#run_cell_select_below').click(function () {
208 IPython.notebook.execute_cell_and_select_below();
199 IPython.notebook.execute_cell_and_select_below();
209 });
200 });
210 this.element.find('#run_cell_insert_below').click(function () {
201 this.element.find('#run_cell_insert_below').click(function () {
211 IPython.notebook.execute_cell_and_insert_below();
202 IPython.notebook.execute_cell_and_insert_below();
212 });
203 });
213 this.element.find('#run_all_cells').click(function () {
204 this.element.find('#run_all_cells').click(function () {
214 IPython.notebook.execute_all_cells();
205 IPython.notebook.execute_all_cells();
215 });
206 });
216 this.element.find('#run_all_cells_above').click(function () {
207 this.element.find('#run_all_cells_above').click(function () {
217 IPython.notebook.execute_cells_above();
208 IPython.notebook.execute_cells_above();
218 });
209 });
219 this.element.find('#run_all_cells_below').click(function () {
210 this.element.find('#run_all_cells_below').click(function () {
220 IPython.notebook.execute_cells_below();
211 IPython.notebook.execute_cells_below();
221 });
212 });
222 this.element.find('#to_code').click(function () {
213 this.element.find('#to_code').click(function () {
223 IPython.notebook.to_code();
214 IPython.notebook.to_code();
224 });
215 });
225 this.element.find('#to_markdown').click(function () {
216 this.element.find('#to_markdown').click(function () {
226 IPython.notebook.to_markdown();
217 IPython.notebook.to_markdown();
227 });
218 });
228 this.element.find('#to_raw').click(function () {
219 this.element.find('#to_raw').click(function () {
229 IPython.notebook.to_raw();
220 IPython.notebook.to_raw();
230 });
221 });
231 this.element.find('#to_heading1').click(function () {
222 this.element.find('#to_heading1').click(function () {
232 IPython.notebook.to_heading(undefined, 1);
223 IPython.notebook.to_heading(undefined, 1);
233 });
224 });
234 this.element.find('#to_heading2').click(function () {
225 this.element.find('#to_heading2').click(function () {
235 IPython.notebook.to_heading(undefined, 2);
226 IPython.notebook.to_heading(undefined, 2);
236 });
227 });
237 this.element.find('#to_heading3').click(function () {
228 this.element.find('#to_heading3').click(function () {
238 IPython.notebook.to_heading(undefined, 3);
229 IPython.notebook.to_heading(undefined, 3);
239 });
230 });
240 this.element.find('#to_heading4').click(function () {
231 this.element.find('#to_heading4').click(function () {
241 IPython.notebook.to_heading(undefined, 4);
232 IPython.notebook.to_heading(undefined, 4);
242 });
233 });
243 this.element.find('#to_heading5').click(function () {
234 this.element.find('#to_heading5').click(function () {
244 IPython.notebook.to_heading(undefined, 5);
235 IPython.notebook.to_heading(undefined, 5);
245 });
236 });
246 this.element.find('#to_heading6').click(function () {
237 this.element.find('#to_heading6').click(function () {
247 IPython.notebook.to_heading(undefined, 6);
238 IPython.notebook.to_heading(undefined, 6);
248 });
239 });
249
240
250 this.element.find('#toggle_current_output').click(function () {
241 this.element.find('#toggle_current_output').click(function () {
251 IPython.notebook.toggle_output();
242 IPython.notebook.toggle_output();
252 });
243 });
253 this.element.find('#toggle_current_output_scroll').click(function () {
244 this.element.find('#toggle_current_output_scroll').click(function () {
254 IPython.notebook.toggle_output_scroll();
245 IPython.notebook.toggle_output_scroll();
255 });
246 });
256 this.element.find('#clear_current_output').click(function () {
247 this.element.find('#clear_current_output').click(function () {
257 IPython.notebook.clear_output();
248 IPython.notebook.clear_output();
258 });
249 });
259
250
260 this.element.find('#toggle_all_output').click(function () {
251 this.element.find('#toggle_all_output').click(function () {
261 IPython.notebook.toggle_all_output();
252 IPython.notebook.toggle_all_output();
262 });
253 });
263 this.element.find('#toggle_all_output_scroll').click(function () {
254 this.element.find('#toggle_all_output_scroll').click(function () {
264 IPython.notebook.toggle_all_output_scroll();
255 IPython.notebook.toggle_all_output_scroll();
265 });
256 });
266 this.element.find('#clear_all_output').click(function () {
257 this.element.find('#clear_all_output').click(function () {
267 IPython.notebook.clear_all_output();
258 IPython.notebook.clear_all_output();
268 });
259 });
269
260
270 // Kernel
261 // Kernel
271 this.element.find('#int_kernel').click(function () {
262 this.element.find('#int_kernel').click(function () {
272 IPython.notebook.session.interrupt_kernel();
263 IPython.notebook.session.interrupt_kernel();
273 });
264 });
274 this.element.find('#restart_kernel').click(function () {
265 this.element.find('#restart_kernel').click(function () {
275 IPython.notebook.restart_kernel();
266 IPython.notebook.restart_kernel();
276 });
267 });
277 // Help
268 // Help
278 this.element.find('#keyboard_shortcuts').click(function () {
269 this.element.find('#keyboard_shortcuts').click(function () {
279 IPython.quick_help.show_keyboard_shortcuts();
270 IPython.quick_help.show_keyboard_shortcuts();
280 });
271 });
281
272
282 this.update_restore_checkpoint(null);
273 this.update_restore_checkpoint(null);
283
274
284 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
275 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
285 that.update_restore_checkpoint(IPython.notebook.checkpoints);
276 that.update_restore_checkpoint(IPython.notebook.checkpoints);
286 });
277 });
287
278
288 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
279 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
289 that.update_restore_checkpoint(IPython.notebook.checkpoints);
280 that.update_restore_checkpoint(IPython.notebook.checkpoints);
290 });
281 });
291 };
282 };
292
283
293 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
284 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
294 var ul = this.element.find("#restore_checkpoint").find("ul");
285 var ul = this.element.find("#restore_checkpoint").find("ul");
295 ul.empty();
286 ul.empty();
296 if (!checkpoints || checkpoints.length === 0) {
287 if (!checkpoints || checkpoints.length === 0) {
297 ul.append(
288 ul.append(
298 $("<li/>")
289 $("<li/>")
299 .addClass("disabled")
290 .addClass("disabled")
300 .append(
291 .append(
301 $("<a/>")
292 $("<a/>")
302 .text("No checkpoints")
293 .text("No checkpoints")
303 )
294 )
304 );
295 );
305 return;
296 return;
306 }
297 }
307
298
308 checkpoints.map(function (checkpoint) {
299 checkpoints.map(function (checkpoint) {
309 var d = new Date(checkpoint.last_modified);
300 var d = new Date(checkpoint.last_modified);
310 ul.append(
301 ul.append(
311 $("<li/>").append(
302 $("<li/>").append(
312 $("<a/>")
303 $("<a/>")
313 .attr("href", "#")
304 .attr("href", "#")
314 .text(d.format("mmm dd HH:MM:ss"))
305 .text(d.format("mmm dd HH:MM:ss"))
315 .click(function () {
306 .click(function () {
316 IPython.notebook.restore_checkpoint_dialog(checkpoint);
307 IPython.notebook.restore_checkpoint_dialog(checkpoint);
317 })
308 })
318 )
309 )
319 );
310 );
320 });
311 });
321 };
312 };
322
313
323 IPython.MenuBar = MenuBar;
314 IPython.MenuBar = MenuBar;
324
315
325 return IPython;
316 return IPython;
326
317
327 }(IPython));
318 }(IPython));
@@ -1,2301 +1,2288 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 /**
17 /**
18 * A notebook contains and manages cells.
18 * A notebook contains and manages cells.
19 *
19 *
20 * @class Notebook
20 * @class Notebook
21 * @constructor
21 * @constructor
22 * @param {String} selector A jQuery selector for the notebook's DOM element
22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {Object} [options] A config object
23 * @param {Object} [options] A config object
24 */
24 */
25 var Notebook = function (selector, options) {
25 var Notebook = function (selector, options) {
26 var options = options || {};
26 this.options = options = options || {};
27 this._baseProjectUrl = options.baseProjectUrl;
27 this.base_url = options.base_url;
28 this.notebook_path = options.notebookPath;
28 this.notebook_path = options.notebook_path;
29 this.notebook_name = options.notebookName;
29 this.notebook_name = options.notebook_name;
30 this.element = $(selector);
30 this.element = $(selector);
31 this.element.scroll();
31 this.element.scroll();
32 this.element.data("notebook", this);
32 this.element.data("notebook", this);
33 this.next_prompt_number = 1;
33 this.next_prompt_number = 1;
34 this.session = null;
34 this.session = null;
35 this.kernel = null;
35 this.kernel = null;
36 this.clipboard = null;
36 this.clipboard = null;
37 this.undelete_backup = null;
37 this.undelete_backup = null;
38 this.undelete_index = null;
38 this.undelete_index = null;
39 this.undelete_below = false;
39 this.undelete_below = false;
40 this.paste_enabled = false;
40 this.paste_enabled = false;
41 // It is important to start out in command mode to match the intial mode
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
42 // of the KeyboardManager.
43 this.mode = 'command';
43 this.mode = 'command';
44 this.set_dirty(false);
44 this.set_dirty(false);
45 this.metadata = {};
45 this.metadata = {};
46 this._checkpoint_after_save = false;
46 this._checkpoint_after_save = false;
47 this.last_checkpoint = null;
47 this.last_checkpoint = null;
48 this.checkpoints = [];
48 this.checkpoints = [];
49 this.autosave_interval = 0;
49 this.autosave_interval = 0;
50 this.autosave_timer = null;
50 this.autosave_timer = null;
51 // autosave *at most* every two minutes
51 // autosave *at most* every two minutes
52 this.minimum_autosave_interval = 120000;
52 this.minimum_autosave_interval = 120000;
53 // single worksheet for now
53 // single worksheet for now
54 this.worksheet_metadata = {};
54 this.worksheet_metadata = {};
55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.notebook_name_blacklist_re = /[\/\\:]/;
56 this.nbformat = 3 // Increment this when changing the nbformat
56 this.nbformat = 3; // Increment this when changing the nbformat
57 this.nbformat_minor = 0 // Increment this when changing the nbformat
57 this.nbformat_minor = 0; // Increment this when changing the nbformat
58 this.style();
58 this.style();
59 this.create_elements();
59 this.create_elements();
60 this.bind_events();
60 this.bind_events();
61 };
61 };
62
62
63 /**
63 /**
64 * Tweak the notebook's CSS style.
64 * Tweak the notebook's CSS style.
65 *
65 *
66 * @method style
66 * @method style
67 */
67 */
68 Notebook.prototype.style = function () {
68 Notebook.prototype.style = function () {
69 $('div#notebook').addClass('border-box-sizing');
69 $('div#notebook').addClass('border-box-sizing');
70 };
70 };
71
71
72 /**
72 /**
73 * Get the root URL of the notebook server.
74 *
75 * @method baseProjectUrl
76 * @return {String} The base project URL
77 */
78 Notebook.prototype.baseProjectUrl = function() {
79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
80 };
81
82 Notebook.prototype.notebookName = function() {
83 return $('body').data('notebookName');
84 };
85
86 Notebook.prototype.notebookPath = function() {
87 return $('body').data('notebookPath');
88 };
89
90 /**
91 * Create an HTML and CSS representation of the notebook.
73 * Create an HTML and CSS representation of the notebook.
92 *
74 *
93 * @method create_elements
75 * @method create_elements
94 */
76 */
95 Notebook.prototype.create_elements = function () {
77 Notebook.prototype.create_elements = function () {
96 var that = this;
78 var that = this;
97 this.element.attr('tabindex','-1');
79 this.element.attr('tabindex','-1');
98 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
80 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
99 // We add this end_space div to the end of the notebook div to:
81 // We add this end_space div to the end of the notebook div to:
100 // i) provide a margin between the last cell and the end of the notebook
82 // i) provide a margin between the last cell and the end of the notebook
101 // ii) to prevent the div from scrolling up when the last cell is being
83 // ii) to prevent the div from scrolling up when the last cell is being
102 // edited, but is too low on the page, which browsers will do automatically.
84 // edited, but is too low on the page, which browsers will do automatically.
103 var end_space = $('<div/>').addClass('end_space');
85 var end_space = $('<div/>').addClass('end_space');
104 end_space.dblclick(function (e) {
86 end_space.dblclick(function (e) {
105 var ncells = that.ncells();
87 var ncells = that.ncells();
106 that.insert_cell_below('code',ncells-1);
88 that.insert_cell_below('code',ncells-1);
107 });
89 });
108 this.element.append(this.container);
90 this.element.append(this.container);
109 this.container.append(end_space);
91 this.container.append(end_space);
110 };
92 };
111
93
112 /**
94 /**
113 * Bind JavaScript events: key presses and custom IPython events.
95 * Bind JavaScript events: key presses and custom IPython events.
114 *
96 *
115 * @method bind_events
97 * @method bind_events
116 */
98 */
117 Notebook.prototype.bind_events = function () {
99 Notebook.prototype.bind_events = function () {
118 var that = this;
100 var that = this;
119
101
120 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
102 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
121 var index = that.find_cell_index(data.cell);
103 var index = that.find_cell_index(data.cell);
122 var new_cell = that.insert_cell_below('code',index);
104 var new_cell = that.insert_cell_below('code',index);
123 new_cell.set_text(data.text);
105 new_cell.set_text(data.text);
124 that.dirty = true;
106 that.dirty = true;
125 });
107 });
126
108
127 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
109 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
128 that.dirty = data.value;
110 that.dirty = data.value;
129 });
111 });
130
112
131 $([IPython.events]).on('select.Cell', function (event, data) {
113 $([IPython.events]).on('select.Cell', function (event, data) {
132 var index = that.find_cell_index(data.cell);
114 var index = that.find_cell_index(data.cell);
133 that.select(index);
115 that.select(index);
134 });
116 });
135
117
136 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
118 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
137 var index = that.find_cell_index(data.cell);
119 var index = that.find_cell_index(data.cell);
138 that.select(index);
120 that.select(index);
139 that.edit_mode();
121 that.edit_mode();
140 });
122 });
141
123
142 $([IPython.events]).on('command_mode.Cell', function (event, data) {
124 $([IPython.events]).on('command_mode.Cell', function (event, data) {
143 that.command_mode();
125 that.command_mode();
144 });
126 });
145
127
146 $([IPython.events]).on('status_autorestarting.Kernel', function () {
128 $([IPython.events]).on('status_autorestarting.Kernel', function () {
147 IPython.dialog.modal({
129 IPython.dialog.modal({
148 title: "Kernel Restarting",
130 title: "Kernel Restarting",
149 body: "The kernel appears to have died. It will restart automatically.",
131 body: "The kernel appears to have died. It will restart automatically.",
150 buttons: {
132 buttons: {
151 OK : {
133 OK : {
152 class : "btn-primary"
134 class : "btn-primary"
153 }
135 }
154 }
136 }
155 });
137 });
156 });
138 });
157
139
158 var collapse_time = function (time) {
140 var collapse_time = function (time) {
159 var app_height = $('#ipython-main-app').height(); // content height
141 var app_height = $('#ipython-main-app').height(); // content height
160 var splitter_height = $('div#pager_splitter').outerHeight(true);
142 var splitter_height = $('div#pager_splitter').outerHeight(true);
161 var new_height = app_height - splitter_height;
143 var new_height = app_height - splitter_height;
162 that.element.animate({height : new_height + 'px'}, time);
144 that.element.animate({height : new_height + 'px'}, time);
163 };
145 };
164
146
165 this.element.bind('collapse_pager', function (event, extrap) {
147 this.element.bind('collapse_pager', function (event, extrap) {
166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
148 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
167 collapse_time(time);
149 collapse_time(time);
168 });
150 });
169
151
170 var expand_time = function (time) {
152 var expand_time = function (time) {
171 var app_height = $('#ipython-main-app').height(); // content height
153 var app_height = $('#ipython-main-app').height(); // content height
172 var splitter_height = $('div#pager_splitter').outerHeight(true);
154 var splitter_height = $('div#pager_splitter').outerHeight(true);
173 var pager_height = $('div#pager').outerHeight(true);
155 var pager_height = $('div#pager').outerHeight(true);
174 var new_height = app_height - pager_height - splitter_height;
156 var new_height = app_height - pager_height - splitter_height;
175 that.element.animate({height : new_height + 'px'}, time);
157 that.element.animate({height : new_height + 'px'}, time);
176 };
158 };
177
159
178 this.element.bind('expand_pager', function (event, extrap) {
160 this.element.bind('expand_pager', function (event, extrap) {
179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
161 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
180 expand_time(time);
162 expand_time(time);
181 });
163 });
182
164
183 // Firefox 22 broke $(window).on("beforeunload")
165 // Firefox 22 broke $(window).on("beforeunload")
184 // I'm not sure why or how.
166 // I'm not sure why or how.
185 window.onbeforeunload = function (e) {
167 window.onbeforeunload = function (e) {
186 // TODO: Make killing the kernel configurable.
168 // TODO: Make killing the kernel configurable.
187 var kill_kernel = false;
169 var kill_kernel = false;
188 if (kill_kernel) {
170 if (kill_kernel) {
189 that.session.kill_kernel();
171 that.session.kill_kernel();
190 }
172 }
191 // if we are autosaving, trigger an autosave on nav-away.
173 // if we are autosaving, trigger an autosave on nav-away.
192 // still warn, because if we don't the autosave may fail.
174 // still warn, because if we don't the autosave may fail.
193 if (that.dirty) {
175 if (that.dirty) {
194 if ( that.autosave_interval ) {
176 if ( that.autosave_interval ) {
195 // schedule autosave in a timeout
177 // schedule autosave in a timeout
196 // this gives you a chance to forcefully discard changes
178 // this gives you a chance to forcefully discard changes
197 // by reloading the page if you *really* want to.
179 // by reloading the page if you *really* want to.
198 // the timer doesn't start until you *dismiss* the dialog.
180 // the timer doesn't start until you *dismiss* the dialog.
199 setTimeout(function () {
181 setTimeout(function () {
200 if (that.dirty) {
182 if (that.dirty) {
201 that.save_notebook();
183 that.save_notebook();
202 }
184 }
203 }, 1000);
185 }, 1000);
204 return "Autosave in progress, latest changes may be lost.";
186 return "Autosave in progress, latest changes may be lost.";
205 } else {
187 } else {
206 return "Unsaved changes will be lost.";
188 return "Unsaved changes will be lost.";
207 }
189 }
208 };
190 }
209 // Null is the *only* return value that will make the browser not
191 // Null is the *only* return value that will make the browser not
210 // pop up the "don't leave" dialog.
192 // pop up the "don't leave" dialog.
211 return null;
193 return null;
212 };
194 };
213 };
195 };
214
196
215 /**
197 /**
216 * Set the dirty flag, and trigger the set_dirty.Notebook event
198 * Set the dirty flag, and trigger the set_dirty.Notebook event
217 *
199 *
218 * @method set_dirty
200 * @method set_dirty
219 */
201 */
220 Notebook.prototype.set_dirty = function (value) {
202 Notebook.prototype.set_dirty = function (value) {
221 if (value === undefined) {
203 if (value === undefined) {
222 value = true;
204 value = true;
223 }
205 }
224 if (this.dirty == value) {
206 if (this.dirty == value) {
225 return;
207 return;
226 }
208 }
227 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
209 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
228 };
210 };
229
211
230 /**
212 /**
231 * Scroll the top of the page to a given cell.
213 * Scroll the top of the page to a given cell.
232 *
214 *
233 * @method scroll_to_cell
215 * @method scroll_to_cell
234 * @param {Number} cell_number An index of the cell to view
216 * @param {Number} cell_number An index of the cell to view
235 * @param {Number} time Animation time in milliseconds
217 * @param {Number} time Animation time in milliseconds
236 * @return {Number} Pixel offset from the top of the container
218 * @return {Number} Pixel offset from the top of the container
237 */
219 */
238 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
220 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
239 var cells = this.get_cells();
221 var cells = this.get_cells();
240 var time = time || 0;
222 time = time || 0;
241 cell_number = Math.min(cells.length-1,cell_number);
223 cell_number = Math.min(cells.length-1,cell_number);
242 cell_number = Math.max(0 ,cell_number);
224 cell_number = Math.max(0 ,cell_number);
243 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
225 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
244 this.element.animate({scrollTop:scroll_value}, time);
226 this.element.animate({scrollTop:scroll_value}, time);
245 return scroll_value;
227 return scroll_value;
246 };
228 };
247
229
248 /**
230 /**
249 * Scroll to the bottom of the page.
231 * Scroll to the bottom of the page.
250 *
232 *
251 * @method scroll_to_bottom
233 * @method scroll_to_bottom
252 */
234 */
253 Notebook.prototype.scroll_to_bottom = function () {
235 Notebook.prototype.scroll_to_bottom = function () {
254 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
236 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
255 };
237 };
256
238
257 /**
239 /**
258 * Scroll to the top of the page.
240 * Scroll to the top of the page.
259 *
241 *
260 * @method scroll_to_top
242 * @method scroll_to_top
261 */
243 */
262 Notebook.prototype.scroll_to_top = function () {
244 Notebook.prototype.scroll_to_top = function () {
263 this.element.animate({scrollTop:0}, 0);
245 this.element.animate({scrollTop:0}, 0);
264 };
246 };
265
247
266 // Edit Notebook metadata
248 // Edit Notebook metadata
267
249
268 Notebook.prototype.edit_metadata = function () {
250 Notebook.prototype.edit_metadata = function () {
269 var that = this;
251 var that = this;
270 IPython.dialog.edit_metadata(this.metadata, function (md) {
252 IPython.dialog.edit_metadata(this.metadata, function (md) {
271 that.metadata = md;
253 that.metadata = md;
272 }, 'Notebook');
254 }, 'Notebook');
273 };
255 };
274
256
275 // Cell indexing, retrieval, etc.
257 // Cell indexing, retrieval, etc.
276
258
277 /**
259 /**
278 * Get all cell elements in the notebook.
260 * Get all cell elements in the notebook.
279 *
261 *
280 * @method get_cell_elements
262 * @method get_cell_elements
281 * @return {jQuery} A selector of all cell elements
263 * @return {jQuery} A selector of all cell elements
282 */
264 */
283 Notebook.prototype.get_cell_elements = function () {
265 Notebook.prototype.get_cell_elements = function () {
284 return this.container.children("div.cell");
266 return this.container.children("div.cell");
285 };
267 };
286
268
287 /**
269 /**
288 * Get a particular cell element.
270 * Get a particular cell element.
289 *
271 *
290 * @method get_cell_element
272 * @method get_cell_element
291 * @param {Number} index An index of a cell to select
273 * @param {Number} index An index of a cell to select
292 * @return {jQuery} A selector of the given cell.
274 * @return {jQuery} A selector of the given cell.
293 */
275 */
294 Notebook.prototype.get_cell_element = function (index) {
276 Notebook.prototype.get_cell_element = function (index) {
295 var result = null;
277 var result = null;
296 var e = this.get_cell_elements().eq(index);
278 var e = this.get_cell_elements().eq(index);
297 if (e.length !== 0) {
279 if (e.length !== 0) {
298 result = e;
280 result = e;
299 }
281 }
300 return result;
282 return result;
301 };
283 };
302
284
303 /**
285 /**
304 * Try to get a particular cell by msg_id.
286 * Try to get a particular cell by msg_id.
305 *
287 *
306 * @method get_msg_cell
288 * @method get_msg_cell
307 * @param {String} msg_id A message UUID
289 * @param {String} msg_id A message UUID
308 * @return {Cell} Cell or null if no cell was found.
290 * @return {Cell} Cell or null if no cell was found.
309 */
291 */
310 Notebook.prototype.get_msg_cell = function (msg_id) {
292 Notebook.prototype.get_msg_cell = function (msg_id) {
311 return IPython.CodeCell.msg_cells[msg_id] || null;
293 return IPython.CodeCell.msg_cells[msg_id] || null;
312 };
294 };
313
295
314 /**
296 /**
315 * Count the cells in this notebook.
297 * Count the cells in this notebook.
316 *
298 *
317 * @method ncells
299 * @method ncells
318 * @return {Number} The number of cells in this notebook
300 * @return {Number} The number of cells in this notebook
319 */
301 */
320 Notebook.prototype.ncells = function () {
302 Notebook.prototype.ncells = function () {
321 return this.get_cell_elements().length;
303 return this.get_cell_elements().length;
322 };
304 };
323
305
324 /**
306 /**
325 * Get all Cell objects in this notebook.
307 * Get all Cell objects in this notebook.
326 *
308 *
327 * @method get_cells
309 * @method get_cells
328 * @return {Array} This notebook's Cell objects
310 * @return {Array} This notebook's Cell objects
329 */
311 */
330 // TODO: we are often calling cells as cells()[i], which we should optimize
312 // TODO: we are often calling cells as cells()[i], which we should optimize
331 // to cells(i) or a new method.
313 // to cells(i) or a new method.
332 Notebook.prototype.get_cells = function () {
314 Notebook.prototype.get_cells = function () {
333 return this.get_cell_elements().toArray().map(function (e) {
315 return this.get_cell_elements().toArray().map(function (e) {
334 return $(e).data("cell");
316 return $(e).data("cell");
335 });
317 });
336 };
318 };
337
319
338 /**
320 /**
339 * Get a Cell object from this notebook.
321 * Get a Cell object from this notebook.
340 *
322 *
341 * @method get_cell
323 * @method get_cell
342 * @param {Number} index An index of a cell to retrieve
324 * @param {Number} index An index of a cell to retrieve
343 * @return {Cell} A particular cell
325 * @return {Cell} A particular cell
344 */
326 */
345 Notebook.prototype.get_cell = function (index) {
327 Notebook.prototype.get_cell = function (index) {
346 var result = null;
328 var result = null;
347 var ce = this.get_cell_element(index);
329 var ce = this.get_cell_element(index);
348 if (ce !== null) {
330 if (ce !== null) {
349 result = ce.data('cell');
331 result = ce.data('cell');
350 }
332 }
351 return result;
333 return result;
352 }
334 };
353
335
354 /**
336 /**
355 * Get the cell below a given cell.
337 * Get the cell below a given cell.
356 *
338 *
357 * @method get_next_cell
339 * @method get_next_cell
358 * @param {Cell} cell The provided cell
340 * @param {Cell} cell The provided cell
359 * @return {Cell} The next cell
341 * @return {Cell} The next cell
360 */
342 */
361 Notebook.prototype.get_next_cell = function (cell) {
343 Notebook.prototype.get_next_cell = function (cell) {
362 var result = null;
344 var result = null;
363 var index = this.find_cell_index(cell);
345 var index = this.find_cell_index(cell);
364 if (this.is_valid_cell_index(index+1)) {
346 if (this.is_valid_cell_index(index+1)) {
365 result = this.get_cell(index+1);
347 result = this.get_cell(index+1);
366 }
348 }
367 return result;
349 return result;
368 }
350 };
369
351
370 /**
352 /**
371 * Get the cell above a given cell.
353 * Get the cell above a given cell.
372 *
354 *
373 * @method get_prev_cell
355 * @method get_prev_cell
374 * @param {Cell} cell The provided cell
356 * @param {Cell} cell The provided cell
375 * @return {Cell} The previous cell
357 * @return {Cell} The previous cell
376 */
358 */
377 Notebook.prototype.get_prev_cell = function (cell) {
359 Notebook.prototype.get_prev_cell = function (cell) {
378 // TODO: off-by-one
360 // TODO: off-by-one
379 // nb.get_prev_cell(nb.get_cell(1)) is null
361 // nb.get_prev_cell(nb.get_cell(1)) is null
380 var result = null;
362 var result = null;
381 var index = this.find_cell_index(cell);
363 var index = this.find_cell_index(cell);
382 if (index !== null && index > 1) {
364 if (index !== null && index > 1) {
383 result = this.get_cell(index-1);
365 result = this.get_cell(index-1);
384 }
366 }
385 return result;
367 return result;
386 }
368 };
387
369
388 /**
370 /**
389 * Get the numeric index of a given cell.
371 * Get the numeric index of a given cell.
390 *
372 *
391 * @method find_cell_index
373 * @method find_cell_index
392 * @param {Cell} cell The provided cell
374 * @param {Cell} cell The provided cell
393 * @return {Number} The cell's numeric index
375 * @return {Number} The cell's numeric index
394 */
376 */
395 Notebook.prototype.find_cell_index = function (cell) {
377 Notebook.prototype.find_cell_index = function (cell) {
396 var result = null;
378 var result = null;
397 this.get_cell_elements().filter(function (index) {
379 this.get_cell_elements().filter(function (index) {
398 if ($(this).data("cell") === cell) {
380 if ($(this).data("cell") === cell) {
399 result = index;
381 result = index;
400 };
382 }
401 });
383 });
402 return result;
384 return result;
403 };
385 };
404
386
405 /**
387 /**
406 * Get a given index , or the selected index if none is provided.
388 * Get a given index , or the selected index if none is provided.
407 *
389 *
408 * @method index_or_selected
390 * @method index_or_selected
409 * @param {Number} index A cell's index
391 * @param {Number} index A cell's index
410 * @return {Number} The given index, or selected index if none is provided.
392 * @return {Number} The given index, or selected index if none is provided.
411 */
393 */
412 Notebook.prototype.index_or_selected = function (index) {
394 Notebook.prototype.index_or_selected = function (index) {
413 var i;
395 var i;
414 if (index === undefined || index === null) {
396 if (index === undefined || index === null) {
415 i = this.get_selected_index();
397 i = this.get_selected_index();
416 if (i === null) {
398 if (i === null) {
417 i = 0;
399 i = 0;
418 }
400 }
419 } else {
401 } else {
420 i = index;
402 i = index;
421 }
403 }
422 return i;
404 return i;
423 };
405 };
424
406
425 /**
407 /**
426 * Get the currently selected cell.
408 * Get the currently selected cell.
427 * @method get_selected_cell
409 * @method get_selected_cell
428 * @return {Cell} The selected cell
410 * @return {Cell} The selected cell
429 */
411 */
430 Notebook.prototype.get_selected_cell = function () {
412 Notebook.prototype.get_selected_cell = function () {
431 var index = this.get_selected_index();
413 var index = this.get_selected_index();
432 return this.get_cell(index);
414 return this.get_cell(index);
433 };
415 };
434
416
435 /**
417 /**
436 * Check whether a cell index is valid.
418 * Check whether a cell index is valid.
437 *
419 *
438 * @method is_valid_cell_index
420 * @method is_valid_cell_index
439 * @param {Number} index A cell index
421 * @param {Number} index A cell index
440 * @return True if the index is valid, false otherwise
422 * @return True if the index is valid, false otherwise
441 */
423 */
442 Notebook.prototype.is_valid_cell_index = function (index) {
424 Notebook.prototype.is_valid_cell_index = function (index) {
443 if (index !== null && index >= 0 && index < this.ncells()) {
425 if (index !== null && index >= 0 && index < this.ncells()) {
444 return true;
426 return true;
445 } else {
427 } else {
446 return false;
428 return false;
447 };
429 }
448 }
430 };
449
431
450 /**
432 /**
451 * Get the index of the currently selected cell.
433 * Get the index of the currently selected cell.
452
434
453 * @method get_selected_index
435 * @method get_selected_index
454 * @return {Number} The selected cell's numeric index
436 * @return {Number} The selected cell's numeric index
455 */
437 */
456 Notebook.prototype.get_selected_index = function () {
438 Notebook.prototype.get_selected_index = function () {
457 var result = null;
439 var result = null;
458 this.get_cell_elements().filter(function (index) {
440 this.get_cell_elements().filter(function (index) {
459 if ($(this).data("cell").selected === true) {
441 if ($(this).data("cell").selected === true) {
460 result = index;
442 result = index;
461 };
443 }
462 });
444 });
463 return result;
445 return result;
464 };
446 };
465
447
466
448
467 // Cell selection.
449 // Cell selection.
468
450
469 /**
451 /**
470 * Programmatically select a cell.
452 * Programmatically select a cell.
471 *
453 *
472 * @method select
454 * @method select
473 * @param {Number} index A cell's index
455 * @param {Number} index A cell's index
474 * @return {Notebook} This notebook
456 * @return {Notebook} This notebook
475 */
457 */
476 Notebook.prototype.select = function (index) {
458 Notebook.prototype.select = function (index) {
477 if (this.is_valid_cell_index(index)) {
459 if (this.is_valid_cell_index(index)) {
478 var sindex = this.get_selected_index()
460 var sindex = this.get_selected_index();
479 if (sindex !== null && index !== sindex) {
461 if (sindex !== null && index !== sindex) {
480 this.command_mode();
462 this.command_mode();
481 this.get_cell(sindex).unselect();
463 this.get_cell(sindex).unselect();
482 };
464 }
483 var cell = this.get_cell(index);
465 var cell = this.get_cell(index);
484 cell.select();
466 cell.select();
485 if (cell.cell_type === 'heading') {
467 if (cell.cell_type === 'heading') {
486 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
468 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
487 {'cell_type':cell.cell_type,level:cell.level}
469 {'cell_type':cell.cell_type,level:cell.level}
488 );
470 );
489 } else {
471 } else {
490 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
472 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
491 {'cell_type':cell.cell_type}
473 {'cell_type':cell.cell_type}
492 );
474 );
493 };
475 }
494 };
476 }
495 return this;
477 return this;
496 };
478 };
497
479
498 /**
480 /**
499 * Programmatically select the next cell.
481 * Programmatically select the next cell.
500 *
482 *
501 * @method select_next
483 * @method select_next
502 * @return {Notebook} This notebook
484 * @return {Notebook} This notebook
503 */
485 */
504 Notebook.prototype.select_next = function () {
486 Notebook.prototype.select_next = function () {
505 var index = this.get_selected_index();
487 var index = this.get_selected_index();
506 this.select(index+1);
488 this.select(index+1);
507 return this;
489 return this;
508 };
490 };
509
491
510 /**
492 /**
511 * Programmatically select the previous cell.
493 * Programmatically select the previous cell.
512 *
494 *
513 * @method select_prev
495 * @method select_prev
514 * @return {Notebook} This notebook
496 * @return {Notebook} This notebook
515 */
497 */
516 Notebook.prototype.select_prev = function () {
498 Notebook.prototype.select_prev = function () {
517 var index = this.get_selected_index();
499 var index = this.get_selected_index();
518 this.select(index-1);
500 this.select(index-1);
519 return this;
501 return this;
520 };
502 };
521
503
522
504
523 // Edit/Command mode
505 // Edit/Command mode
524
506
525 Notebook.prototype.get_edit_index = function () {
507 Notebook.prototype.get_edit_index = function () {
526 var result = null;
508 var result = null;
527 this.get_cell_elements().filter(function (index) {
509 this.get_cell_elements().filter(function (index) {
528 if ($(this).data("cell").mode === 'edit') {
510 if ($(this).data("cell").mode === 'edit') {
529 result = index;
511 result = index;
530 };
512 }
531 });
513 });
532 return result;
514 return result;
533 };
515 };
534
516
535 Notebook.prototype.command_mode = function () {
517 Notebook.prototype.command_mode = function () {
536 if (this.mode !== 'command') {
518 if (this.mode !== 'command') {
519 $([IPython.events]).trigger('command_mode.Notebook');
537 var index = this.get_edit_index();
520 var index = this.get_edit_index();
538 var cell = this.get_cell(index);
521 var cell = this.get_cell(index);
539 if (cell) {
522 if (cell) {
540 cell.command_mode();
523 cell.command_mode();
541 };
524 }
542 this.mode = 'command';
525 this.mode = 'command';
543 IPython.keyboard_manager.command_mode();
526 IPython.keyboard_manager.command_mode();
544 };
527 }
545 };
528 };
546
529
547 Notebook.prototype.edit_mode = function () {
530 Notebook.prototype.edit_mode = function () {
548 if (this.mode !== 'edit') {
531 if (this.mode !== 'edit') {
532 $([IPython.events]).trigger('edit_mode.Notebook');
549 var cell = this.get_selected_cell();
533 var cell = this.get_selected_cell();
550 if (cell === null) {return;} // No cell is selected
534 if (cell === null) {return;} // No cell is selected
551 // We need to set the mode to edit to prevent reentering this method
535 // We need to set the mode to edit to prevent reentering this method
552 // when cell.edit_mode() is called below.
536 // when cell.edit_mode() is called below.
553 this.mode = 'edit';
537 this.mode = 'edit';
554 IPython.keyboard_manager.edit_mode();
538 IPython.keyboard_manager.edit_mode();
555 cell.edit_mode();
539 cell.edit_mode();
556 };
540 }
557 };
541 };
558
542
559 Notebook.prototype.focus_cell = function () {
543 Notebook.prototype.focus_cell = function () {
560 var cell = this.get_selected_cell();
544 var cell = this.get_selected_cell();
561 if (cell === null) {return;} // No cell is selected
545 if (cell === null) {return;} // No cell is selected
562 cell.focus_cell();
546 cell.focus_cell();
563 };
547 };
564
548
565 // Cell movement
549 // Cell movement
566
550
567 /**
551 /**
568 * Move given (or selected) cell up and select it.
552 * Move given (or selected) cell up and select it.
569 *
553 *
570 * @method move_cell_up
554 * @method move_cell_up
571 * @param [index] {integer} cell index
555 * @param [index] {integer} cell index
572 * @return {Notebook} This notebook
556 * @return {Notebook} This notebook
573 **/
557 **/
574 Notebook.prototype.move_cell_up = function (index) {
558 Notebook.prototype.move_cell_up = function (index) {
575 var i = this.index_or_selected(index);
559 var i = this.index_or_selected(index);
576 if (this.is_valid_cell_index(i) && i > 0) {
560 if (this.is_valid_cell_index(i) && i > 0) {
577 var pivot = this.get_cell_element(i-1);
561 var pivot = this.get_cell_element(i-1);
578 var tomove = this.get_cell_element(i);
562 var tomove = this.get_cell_element(i);
579 if (pivot !== null && tomove !== null) {
563 if (pivot !== null && tomove !== null) {
580 tomove.detach();
564 tomove.detach();
581 pivot.before(tomove);
565 pivot.before(tomove);
582 this.select(i-1);
566 this.select(i-1);
583 var cell = this.get_selected_cell();
567 var cell = this.get_selected_cell();
584 cell.focus_cell();
568 cell.focus_cell();
585 };
569 }
586 this.set_dirty(true);
570 this.set_dirty(true);
587 };
571 }
588 return this;
572 return this;
589 };
573 };
590
574
591
575
592 /**
576 /**
593 * Move given (or selected) cell down and select it
577 * Move given (or selected) cell down and select it
594 *
578 *
595 * @method move_cell_down
579 * @method move_cell_down
596 * @param [index] {integer} cell index
580 * @param [index] {integer} cell index
597 * @return {Notebook} This notebook
581 * @return {Notebook} This notebook
598 **/
582 **/
599 Notebook.prototype.move_cell_down = function (index) {
583 Notebook.prototype.move_cell_down = function (index) {
600 var i = this.index_or_selected(index);
584 var i = this.index_or_selected(index);
601 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
585 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
602 var pivot = this.get_cell_element(i+1);
586 var pivot = this.get_cell_element(i+1);
603 var tomove = this.get_cell_element(i);
587 var tomove = this.get_cell_element(i);
604 if (pivot !== null && tomove !== null) {
588 if (pivot !== null && tomove !== null) {
605 tomove.detach();
589 tomove.detach();
606 pivot.after(tomove);
590 pivot.after(tomove);
607 this.select(i+1);
591 this.select(i+1);
608 var cell = this.get_selected_cell();
592 var cell = this.get_selected_cell();
609 cell.focus_cell();
593 cell.focus_cell();
610 };
594 }
611 };
595 }
612 this.set_dirty();
596 this.set_dirty();
613 return this;
597 return this;
614 };
598 };
615
599
616
600
617 // Insertion, deletion.
601 // Insertion, deletion.
618
602
619 /**
603 /**
620 * Delete a cell from the notebook.
604 * Delete a cell from the notebook.
621 *
605 *
622 * @method delete_cell
606 * @method delete_cell
623 * @param [index] A cell's numeric index
607 * @param [index] A cell's numeric index
624 * @return {Notebook} This notebook
608 * @return {Notebook} This notebook
625 */
609 */
626 Notebook.prototype.delete_cell = function (index) {
610 Notebook.prototype.delete_cell = function (index) {
627 var i = this.index_or_selected(index);
611 var i = this.index_or_selected(index);
628 var cell = this.get_selected_cell();
612 var cell = this.get_selected_cell();
629 this.undelete_backup = cell.toJSON();
613 this.undelete_backup = cell.toJSON();
630 $('#undelete_cell').removeClass('disabled');
614 $('#undelete_cell').removeClass('disabled');
631 if (this.is_valid_cell_index(i)) {
615 if (this.is_valid_cell_index(i)) {
632 var old_ncells = this.ncells();
616 var old_ncells = this.ncells();
633 var ce = this.get_cell_element(i);
617 var ce = this.get_cell_element(i);
634 ce.remove();
618 ce.remove();
635 if (i === 0) {
619 if (i === 0) {
636 // Always make sure we have at least one cell.
620 // Always make sure we have at least one cell.
637 if (old_ncells === 1) {
621 if (old_ncells === 1) {
638 this.insert_cell_below('code');
622 this.insert_cell_below('code');
639 }
623 }
640 this.select(0);
624 this.select(0);
641 this.undelete_index = 0;
625 this.undelete_index = 0;
642 this.undelete_below = false;
626 this.undelete_below = false;
643 } else if (i === old_ncells-1 && i !== 0) {
627 } else if (i === old_ncells-1 && i !== 0) {
644 this.select(i-1);
628 this.select(i-1);
645 this.undelete_index = i - 1;
629 this.undelete_index = i - 1;
646 this.undelete_below = true;
630 this.undelete_below = true;
647 } else {
631 } else {
648 this.select(i);
632 this.select(i);
649 this.undelete_index = i;
633 this.undelete_index = i;
650 this.undelete_below = false;
634 this.undelete_below = false;
651 };
635 }
652 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
636 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
653 this.set_dirty(true);
637 this.set_dirty(true);
654 };
638 }
655 return this;
639 return this;
656 };
640 };
657
641
658 /**
642 /**
659 * Restore the most recently deleted cell.
643 * Restore the most recently deleted cell.
660 *
644 *
661 * @method undelete
645 * @method undelete
662 */
646 */
663 Notebook.prototype.undelete_cell = function() {
647 Notebook.prototype.undelete_cell = function() {
664 if (this.undelete_backup !== null && this.undelete_index !== null) {
648 if (this.undelete_backup !== null && this.undelete_index !== null) {
665 var current_index = this.get_selected_index();
649 var current_index = this.get_selected_index();
666 if (this.undelete_index < current_index) {
650 if (this.undelete_index < current_index) {
667 current_index = current_index + 1;
651 current_index = current_index + 1;
668 }
652 }
669 if (this.undelete_index >= this.ncells()) {
653 if (this.undelete_index >= this.ncells()) {
670 this.select(this.ncells() - 1);
654 this.select(this.ncells() - 1);
671 }
655 }
672 else {
656 else {
673 this.select(this.undelete_index);
657 this.select(this.undelete_index);
674 }
658 }
675 var cell_data = this.undelete_backup;
659 var cell_data = this.undelete_backup;
676 var new_cell = null;
660 var new_cell = null;
677 if (this.undelete_below) {
661 if (this.undelete_below) {
678 new_cell = this.insert_cell_below(cell_data.cell_type);
662 new_cell = this.insert_cell_below(cell_data.cell_type);
679 } else {
663 } else {
680 new_cell = this.insert_cell_above(cell_data.cell_type);
664 new_cell = this.insert_cell_above(cell_data.cell_type);
681 }
665 }
682 new_cell.fromJSON(cell_data);
666 new_cell.fromJSON(cell_data);
683 if (this.undelete_below) {
667 if (this.undelete_below) {
684 this.select(current_index+1);
668 this.select(current_index+1);
685 } else {
669 } else {
686 this.select(current_index);
670 this.select(current_index);
687 }
671 }
688 this.undelete_backup = null;
672 this.undelete_backup = null;
689 this.undelete_index = null;
673 this.undelete_index = null;
690 }
674 }
691 $('#undelete_cell').addClass('disabled');
675 $('#undelete_cell').addClass('disabled');
692 }
676 };
693
677
694 /**
678 /**
695 * Insert a cell so that after insertion the cell is at given index.
679 * Insert a cell so that after insertion the cell is at given index.
696 *
680 *
697 * Similar to insert_above, but index parameter is mandatory
681 * Similar to insert_above, but index parameter is mandatory
698 *
682 *
699 * Index will be brought back into the accissible range [0,n]
683 * Index will be brought back into the accissible range [0,n]
700 *
684 *
701 * @method insert_cell_at_index
685 * @method insert_cell_at_index
702 * @param type {string} in ['code','markdown','heading']
686 * @param type {string} in ['code','markdown','heading']
703 * @param [index] {int} a valid index where to inser cell
687 * @param [index] {int} a valid index where to inser cell
704 *
688 *
705 * @return cell {cell|null} created cell or null
689 * @return cell {cell|null} created cell or null
706 **/
690 **/
707 Notebook.prototype.insert_cell_at_index = function(type, index){
691 Notebook.prototype.insert_cell_at_index = function(type, index){
708
692
709 var ncells = this.ncells();
693 var ncells = this.ncells();
710 var index = Math.min(index,ncells);
694 index = Math.min(index,ncells);
711 index = Math.max(index,0);
695 index = Math.max(index,0);
712 var cell = null;
696 var cell = null;
713
697
714 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
698 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
715 if (type === 'code') {
699 if (type === 'code') {
716 cell = new IPython.CodeCell(this.kernel);
700 cell = new IPython.CodeCell(this.kernel);
717 cell.set_input_prompt();
701 cell.set_input_prompt();
718 } else if (type === 'markdown') {
702 } else if (type === 'markdown') {
719 cell = new IPython.MarkdownCell();
703 cell = new IPython.MarkdownCell();
720 } else if (type === 'raw') {
704 } else if (type === 'raw') {
721 cell = new IPython.RawCell();
705 cell = new IPython.RawCell();
722 } else if (type === 'heading') {
706 } else if (type === 'heading') {
723 cell = new IPython.HeadingCell();
707 cell = new IPython.HeadingCell();
724 }
708 }
725
709
726 if(this._insert_element_at_index(cell.element,index)) {
710 if(this._insert_element_at_index(cell.element,index)) {
727 cell.render();
711 cell.render();
728 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
712 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
729 cell.refresh();
713 cell.refresh();
730 // We used to select the cell after we refresh it, but there
714 // We used to select the cell after we refresh it, but there
731 // are now cases were this method is called where select is
715 // are now cases were this method is called where select is
732 // not appropriate. The selection logic should be handled by the
716 // not appropriate. The selection logic should be handled by the
733 // caller of the the top level insert_cell methods.
717 // caller of the the top level insert_cell methods.
734 this.set_dirty(true);
718 this.set_dirty(true);
735 }
719 }
736 }
720 }
737 return cell;
721 return cell;
738
722
739 };
723 };
740
724
741 /**
725 /**
742 * Insert an element at given cell index.
726 * Insert an element at given cell index.
743 *
727 *
744 * @method _insert_element_at_index
728 * @method _insert_element_at_index
745 * @param element {dom element} a cell element
729 * @param element {dom element} a cell element
746 * @param [index] {int} a valid index where to inser cell
730 * @param [index] {int} a valid index where to inser cell
747 * @private
731 * @private
748 *
732 *
749 * return true if everything whent fine.
733 * return true if everything whent fine.
750 **/
734 **/
751 Notebook.prototype._insert_element_at_index = function(element, index){
735 Notebook.prototype._insert_element_at_index = function(element, index){
752 if (element === undefined){
736 if (element === undefined){
753 return false;
737 return false;
754 }
738 }
755
739
756 var ncells = this.ncells();
740 var ncells = this.ncells();
757
741
758 if (ncells === 0) {
742 if (ncells === 0) {
759 // special case append if empty
743 // special case append if empty
760 this.element.find('div.end_space').before(element);
744 this.element.find('div.end_space').before(element);
761 } else if ( ncells === index ) {
745 } else if ( ncells === index ) {
762 // special case append it the end, but not empty
746 // special case append it the end, but not empty
763 this.get_cell_element(index-1).after(element);
747 this.get_cell_element(index-1).after(element);
764 } else if (this.is_valid_cell_index(index)) {
748 } else if (this.is_valid_cell_index(index)) {
765 // otherwise always somewhere to append to
749 // otherwise always somewhere to append to
766 this.get_cell_element(index).before(element);
750 this.get_cell_element(index).before(element);
767 } else {
751 } else {
768 return false;
752 return false;
769 }
753 }
770
754
771 if (this.undelete_index !== null && index <= this.undelete_index) {
755 if (this.undelete_index !== null && index <= this.undelete_index) {
772 this.undelete_index = this.undelete_index + 1;
756 this.undelete_index = this.undelete_index + 1;
773 this.set_dirty(true);
757 this.set_dirty(true);
774 }
758 }
775 return true;
759 return true;
776 };
760 };
777
761
778 /**
762 /**
779 * Insert a cell of given type above given index, or at top
763 * Insert a cell of given type above given index, or at top
780 * of notebook if index smaller than 0.
764 * of notebook if index smaller than 0.
781 *
765 *
782 * default index value is the one of currently selected cell
766 * default index value is the one of currently selected cell
783 *
767 *
784 * @method insert_cell_above
768 * @method insert_cell_above
785 * @param type {string} cell type
769 * @param type {string} cell type
786 * @param [index] {integer}
770 * @param [index] {integer}
787 *
771 *
788 * @return handle to created cell or null
772 * @return handle to created cell or null
789 **/
773 **/
790 Notebook.prototype.insert_cell_above = function (type, index) {
774 Notebook.prototype.insert_cell_above = function (type, index) {
791 index = this.index_or_selected(index);
775 index = this.index_or_selected(index);
792 return this.insert_cell_at_index(type, index);
776 return this.insert_cell_at_index(type, index);
793 };
777 };
794
778
795 /**
779 /**
796 * Insert a cell of given type below given index, or at bottom
780 * Insert a cell of given type below given index, or at bottom
797 * of notebook if index greater thatn number of cell
781 * of notebook if index greater thatn number of cell
798 *
782 *
799 * default index value is the one of currently selected cell
783 * default index value is the one of currently selected cell
800 *
784 *
801 * @method insert_cell_below
785 * @method insert_cell_below
802 * @param type {string} cell type
786 * @param type {string} cell type
803 * @param [index] {integer}
787 * @param [index] {integer}
804 *
788 *
805 * @return handle to created cell or null
789 * @return handle to created cell or null
806 *
790 *
807 **/
791 **/
808 Notebook.prototype.insert_cell_below = function (type, index) {
792 Notebook.prototype.insert_cell_below = function (type, index) {
809 index = this.index_or_selected(index);
793 index = this.index_or_selected(index);
810 return this.insert_cell_at_index(type, index+1);
794 return this.insert_cell_at_index(type, index+1);
811 };
795 };
812
796
813
797
814 /**
798 /**
815 * Insert cell at end of notebook
799 * Insert cell at end of notebook
816 *
800 *
817 * @method insert_cell_at_bottom
801 * @method insert_cell_at_bottom
818 * @param {String} type cell type
802 * @param {String} type cell type
819 *
803 *
820 * @return the added cell; or null
804 * @return the added cell; or null
821 **/
805 **/
822 Notebook.prototype.insert_cell_at_bottom = function (type){
806 Notebook.prototype.insert_cell_at_bottom = function (type){
823 var len = this.ncells();
807 var len = this.ncells();
824 return this.insert_cell_below(type,len-1);
808 return this.insert_cell_below(type,len-1);
825 };
809 };
826
810
827 /**
811 /**
828 * Turn a cell into a code cell.
812 * Turn a cell into a code cell.
829 *
813 *
830 * @method to_code
814 * @method to_code
831 * @param {Number} [index] A cell's index
815 * @param {Number} [index] A cell's index
832 */
816 */
833 Notebook.prototype.to_code = function (index) {
817 Notebook.prototype.to_code = function (index) {
834 var i = this.index_or_selected(index);
818 var i = this.index_or_selected(index);
835 if (this.is_valid_cell_index(i)) {
819 if (this.is_valid_cell_index(i)) {
836 var source_element = this.get_cell_element(i);
820 var source_element = this.get_cell_element(i);
837 var source_cell = source_element.data("cell");
821 var source_cell = source_element.data("cell");
838 if (!(source_cell instanceof IPython.CodeCell)) {
822 if (!(source_cell instanceof IPython.CodeCell)) {
839 var target_cell = this.insert_cell_below('code',i);
823 var target_cell = this.insert_cell_below('code',i);
840 var text = source_cell.get_text();
824 var text = source_cell.get_text();
841 if (text === source_cell.placeholder) {
825 if (text === source_cell.placeholder) {
842 text = '';
826 text = '';
843 }
827 }
844 target_cell.set_text(text);
828 target_cell.set_text(text);
845 // make this value the starting point, so that we can only undo
829 // make this value the starting point, so that we can only undo
846 // to this state, instead of a blank cell
830 // to this state, instead of a blank cell
847 target_cell.code_mirror.clearHistory();
831 target_cell.code_mirror.clearHistory();
848 source_element.remove();
832 source_element.remove();
849 this.select(i);
833 this.select(i);
850 this.set_dirty(true);
834 this.set_dirty(true);
851 };
835 }
852 };
836 }
853 };
837 };
854
838
855 /**
839 /**
856 * Turn a cell into a Markdown cell.
840 * Turn a cell into a Markdown cell.
857 *
841 *
858 * @method to_markdown
842 * @method to_markdown
859 * @param {Number} [index] A cell's index
843 * @param {Number} [index] A cell's index
860 */
844 */
861 Notebook.prototype.to_markdown = function (index) {
845 Notebook.prototype.to_markdown = function (index) {
862 var i = this.index_or_selected(index);
846 var i = this.index_or_selected(index);
863 if (this.is_valid_cell_index(i)) {
847 if (this.is_valid_cell_index(i)) {
864 var source_element = this.get_cell_element(i);
848 var source_element = this.get_cell_element(i);
865 var source_cell = source_element.data("cell");
849 var source_cell = source_element.data("cell");
866 if (!(source_cell instanceof IPython.MarkdownCell)) {
850 if (!(source_cell instanceof IPython.MarkdownCell)) {
867 var target_cell = this.insert_cell_below('markdown',i);
851 var target_cell = this.insert_cell_below('markdown',i);
868 var text = source_cell.get_text();
852 var text = source_cell.get_text();
869 if (text === source_cell.placeholder) {
853 if (text === source_cell.placeholder) {
870 text = '';
854 text = '';
871 };
855 }
872 // We must show the editor before setting its contents
856 // We must show the editor before setting its contents
873 target_cell.unrender();
857 target_cell.unrender();
874 target_cell.set_text(text);
858 target_cell.set_text(text);
875 // make this value the starting point, so that we can only undo
859 // make this value the starting point, so that we can only undo
876 // to this state, instead of a blank cell
860 // to this state, instead of a blank cell
877 target_cell.code_mirror.clearHistory();
861 target_cell.code_mirror.clearHistory();
878 source_element.remove();
862 source_element.remove();
879 this.select(i);
863 this.select(i);
880 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
864 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
881 target_cell.render();
865 target_cell.render();
882 }
866 }
883 this.set_dirty(true);
867 this.set_dirty(true);
884 };
868 }
885 };
869 }
886 };
870 };
887
871
888 /**
872 /**
889 * Turn a cell into a raw text cell.
873 * Turn a cell into a raw text cell.
890 *
874 *
891 * @method to_raw
875 * @method to_raw
892 * @param {Number} [index] A cell's index
876 * @param {Number} [index] A cell's index
893 */
877 */
894 Notebook.prototype.to_raw = function (index) {
878 Notebook.prototype.to_raw = function (index) {
895 var i = this.index_or_selected(index);
879 var i = this.index_or_selected(index);
896 if (this.is_valid_cell_index(i)) {
880 if (this.is_valid_cell_index(i)) {
897 var source_element = this.get_cell_element(i);
881 var source_element = this.get_cell_element(i);
898 var source_cell = source_element.data("cell");
882 var source_cell = source_element.data("cell");
899 var target_cell = null;
883 var target_cell = null;
900 if (!(source_cell instanceof IPython.RawCell)) {
884 if (!(source_cell instanceof IPython.RawCell)) {
901 target_cell = this.insert_cell_below('raw',i);
885 target_cell = this.insert_cell_below('raw',i);
902 var text = source_cell.get_text();
886 var text = source_cell.get_text();
903 if (text === source_cell.placeholder) {
887 if (text === source_cell.placeholder) {
904 text = '';
888 text = '';
905 };
889 }
906 // We must show the editor before setting its contents
890 // We must show the editor before setting its contents
907 target_cell.unrender();
891 target_cell.unrender();
908 target_cell.set_text(text);
892 target_cell.set_text(text);
909 // make this value the starting point, so that we can only undo
893 // make this value the starting point, so that we can only undo
910 // to this state, instead of a blank cell
894 // to this state, instead of a blank cell
911 target_cell.code_mirror.clearHistory();
895 target_cell.code_mirror.clearHistory();
912 source_element.remove();
896 source_element.remove();
913 this.select(i);
897 this.select(i);
914 this.set_dirty(true);
898 this.set_dirty(true);
915 };
899 }
916 };
900 }
917 };
901 };
918
902
919 /**
903 /**
920 * Turn a cell into a heading cell.
904 * Turn a cell into a heading cell.
921 *
905 *
922 * @method to_heading
906 * @method to_heading
923 * @param {Number} [index] A cell's index
907 * @param {Number} [index] A cell's index
924 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
908 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
925 */
909 */
926 Notebook.prototype.to_heading = function (index, level) {
910 Notebook.prototype.to_heading = function (index, level) {
927 level = level || 1;
911 level = level || 1;
928 var i = this.index_or_selected(index);
912 var i = this.index_or_selected(index);
929 if (this.is_valid_cell_index(i)) {
913 if (this.is_valid_cell_index(i)) {
930 var source_element = this.get_cell_element(i);
914 var source_element = this.get_cell_element(i);
931 var source_cell = source_element.data("cell");
915 var source_cell = source_element.data("cell");
932 var target_cell = null;
916 var target_cell = null;
933 if (source_cell instanceof IPython.HeadingCell) {
917 if (source_cell instanceof IPython.HeadingCell) {
934 source_cell.set_level(level);
918 source_cell.set_level(level);
935 } else {
919 } else {
936 target_cell = this.insert_cell_below('heading',i);
920 target_cell = this.insert_cell_below('heading',i);
937 var text = source_cell.get_text();
921 var text = source_cell.get_text();
938 if (text === source_cell.placeholder) {
922 if (text === source_cell.placeholder) {
939 text = '';
923 text = '';
940 };
924 }
941 // We must show the editor before setting its contents
925 // We must show the editor before setting its contents
942 target_cell.set_level(level);
926 target_cell.set_level(level);
943 target_cell.unrender();
927 target_cell.unrender();
944 target_cell.set_text(text);
928 target_cell.set_text(text);
945 // make this value the starting point, so that we can only undo
929 // make this value the starting point, so that we can only undo
946 // to this state, instead of a blank cell
930 // to this state, instead of a blank cell
947 target_cell.code_mirror.clearHistory();
931 target_cell.code_mirror.clearHistory();
948 source_element.remove();
932 source_element.remove();
949 this.select(i);
933 this.select(i);
950 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
934 if ((source_cell instanceof IPython.TextCell) && source_cell.rendered) {
951 target_cell.render();
935 target_cell.render();
952 }
936 }
953 };
937 }
954 this.set_dirty(true);
938 this.set_dirty(true);
955 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
939 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
956 {'cell_type':'heading',level:level}
940 {'cell_type':'heading',level:level}
957 );
941 );
958 };
942 }
959 };
943 };
960
944
961
945
962 // Cut/Copy/Paste
946 // Cut/Copy/Paste
963
947
964 /**
948 /**
965 * Enable UI elements for pasting cells.
949 * Enable UI elements for pasting cells.
966 *
950 *
967 * @method enable_paste
951 * @method enable_paste
968 */
952 */
969 Notebook.prototype.enable_paste = function () {
953 Notebook.prototype.enable_paste = function () {
970 var that = this;
954 var that = this;
971 if (!this.paste_enabled) {
955 if (!this.paste_enabled) {
972 $('#paste_cell_replace').removeClass('disabled')
956 $('#paste_cell_replace').removeClass('disabled')
973 .on('click', function () {that.paste_cell_replace();});
957 .on('click', function () {that.paste_cell_replace();});
974 $('#paste_cell_above').removeClass('disabled')
958 $('#paste_cell_above').removeClass('disabled')
975 .on('click', function () {that.paste_cell_above();});
959 .on('click', function () {that.paste_cell_above();});
976 $('#paste_cell_below').removeClass('disabled')
960 $('#paste_cell_below').removeClass('disabled')
977 .on('click', function () {that.paste_cell_below();});
961 .on('click', function () {that.paste_cell_below();});
978 this.paste_enabled = true;
962 this.paste_enabled = true;
979 };
963 }
980 };
964 };
981
965
982 /**
966 /**
983 * Disable UI elements for pasting cells.
967 * Disable UI elements for pasting cells.
984 *
968 *
985 * @method disable_paste
969 * @method disable_paste
986 */
970 */
987 Notebook.prototype.disable_paste = function () {
971 Notebook.prototype.disable_paste = function () {
988 if (this.paste_enabled) {
972 if (this.paste_enabled) {
989 $('#paste_cell_replace').addClass('disabled').off('click');
973 $('#paste_cell_replace').addClass('disabled').off('click');
990 $('#paste_cell_above').addClass('disabled').off('click');
974 $('#paste_cell_above').addClass('disabled').off('click');
991 $('#paste_cell_below').addClass('disabled').off('click');
975 $('#paste_cell_below').addClass('disabled').off('click');
992 this.paste_enabled = false;
976 this.paste_enabled = false;
993 };
977 }
994 };
978 };
995
979
996 /**
980 /**
997 * Cut a cell.
981 * Cut a cell.
998 *
982 *
999 * @method cut_cell
983 * @method cut_cell
1000 */
984 */
1001 Notebook.prototype.cut_cell = function () {
985 Notebook.prototype.cut_cell = function () {
1002 this.copy_cell();
986 this.copy_cell();
1003 this.delete_cell();
987 this.delete_cell();
1004 }
988 };
1005
989
1006 /**
990 /**
1007 * Copy a cell.
991 * Copy a cell.
1008 *
992 *
1009 * @method copy_cell
993 * @method copy_cell
1010 */
994 */
1011 Notebook.prototype.copy_cell = function () {
995 Notebook.prototype.copy_cell = function () {
1012 var cell = this.get_selected_cell();
996 var cell = this.get_selected_cell();
1013 this.clipboard = cell.toJSON();
997 this.clipboard = cell.toJSON();
1014 this.enable_paste();
998 this.enable_paste();
1015 };
999 };
1016
1000
1017 /**
1001 /**
1018 * Replace the selected cell with a cell in the clipboard.
1002 * Replace the selected cell with a cell in the clipboard.
1019 *
1003 *
1020 * @method paste_cell_replace
1004 * @method paste_cell_replace
1021 */
1005 */
1022 Notebook.prototype.paste_cell_replace = function () {
1006 Notebook.prototype.paste_cell_replace = function () {
1023 if (this.clipboard !== null && this.paste_enabled) {
1007 if (this.clipboard !== null && this.paste_enabled) {
1024 var cell_data = this.clipboard;
1008 var cell_data = this.clipboard;
1025 var new_cell = this.insert_cell_above(cell_data.cell_type);
1009 var new_cell = this.insert_cell_above(cell_data.cell_type);
1026 new_cell.fromJSON(cell_data);
1010 new_cell.fromJSON(cell_data);
1027 var old_cell = this.get_next_cell(new_cell);
1011 var old_cell = this.get_next_cell(new_cell);
1028 this.delete_cell(this.find_cell_index(old_cell));
1012 this.delete_cell(this.find_cell_index(old_cell));
1029 this.select(this.find_cell_index(new_cell));
1013 this.select(this.find_cell_index(new_cell));
1030 };
1014 }
1031 };
1015 };
1032
1016
1033 /**
1017 /**
1034 * Paste a cell from the clipboard above the selected cell.
1018 * Paste a cell from the clipboard above the selected cell.
1035 *
1019 *
1036 * @method paste_cell_above
1020 * @method paste_cell_above
1037 */
1021 */
1038 Notebook.prototype.paste_cell_above = function () {
1022 Notebook.prototype.paste_cell_above = function () {
1039 if (this.clipboard !== null && this.paste_enabled) {
1023 if (this.clipboard !== null && this.paste_enabled) {
1040 var cell_data = this.clipboard;
1024 var cell_data = this.clipboard;
1041 var new_cell = this.insert_cell_above(cell_data.cell_type);
1025 var new_cell = this.insert_cell_above(cell_data.cell_type);
1042 new_cell.fromJSON(cell_data);
1026 new_cell.fromJSON(cell_data);
1043 new_cell.focus_cell();
1027 new_cell.focus_cell();
1044 };
1028 }
1045 };
1029 };
1046
1030
1047 /**
1031 /**
1048 * Paste a cell from the clipboard below the selected cell.
1032 * Paste a cell from the clipboard below the selected cell.
1049 *
1033 *
1050 * @method paste_cell_below
1034 * @method paste_cell_below
1051 */
1035 */
1052 Notebook.prototype.paste_cell_below = function () {
1036 Notebook.prototype.paste_cell_below = function () {
1053 if (this.clipboard !== null && this.paste_enabled) {
1037 if (this.clipboard !== null && this.paste_enabled) {
1054 var cell_data = this.clipboard;
1038 var cell_data = this.clipboard;
1055 var new_cell = this.insert_cell_below(cell_data.cell_type);
1039 var new_cell = this.insert_cell_below(cell_data.cell_type);
1056 new_cell.fromJSON(cell_data);
1040 new_cell.fromJSON(cell_data);
1057 new_cell.focus_cell();
1041 new_cell.focus_cell();
1058 };
1042 }
1059 };
1043 };
1060
1044
1061 // Split/merge
1045 // Split/merge
1062
1046
1063 /**
1047 /**
1064 * Split the selected cell into two, at the cursor.
1048 * Split the selected cell into two, at the cursor.
1065 *
1049 *
1066 * @method split_cell
1050 * @method split_cell
1067 */
1051 */
1068 Notebook.prototype.split_cell = function () {
1052 Notebook.prototype.split_cell = function () {
1069 var mdc = IPython.MarkdownCell;
1053 var mdc = IPython.MarkdownCell;
1070 var rc = IPython.RawCell;
1054 var rc = IPython.RawCell;
1071 var cell = this.get_selected_cell();
1055 var cell = this.get_selected_cell();
1072 if (cell.is_splittable()) {
1056 if (cell.is_splittable()) {
1073 var texta = cell.get_pre_cursor();
1057 var texta = cell.get_pre_cursor();
1074 var textb = cell.get_post_cursor();
1058 var textb = cell.get_post_cursor();
1075 if (cell instanceof IPython.CodeCell) {
1059 if (cell instanceof IPython.CodeCell) {
1076 // In this case the operations keep the notebook in its existing mode
1060 // In this case the operations keep the notebook in its existing mode
1077 // so we don't need to do any post-op mode changes.
1061 // so we don't need to do any post-op mode changes.
1078 cell.set_text(textb);
1062 cell.set_text(textb);
1079 var new_cell = this.insert_cell_above('code');
1063 var new_cell = this.insert_cell_above('code');
1080 new_cell.set_text(texta);
1064 new_cell.set_text(texta);
1081 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1065 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1082 // We know cell is !rendered so we can use set_text.
1066 // We know cell is !rendered so we can use set_text.
1083 cell.set_text(textb);
1067 cell.set_text(textb);
1084 var new_cell = this.insert_cell_above(cell.cell_type);
1068 var new_cell = this.insert_cell_above(cell.cell_type);
1085 // Unrender the new cell so we can call set_text.
1069 // Unrender the new cell so we can call set_text.
1086 new_cell.unrender();
1070 new_cell.unrender();
1087 new_cell.set_text(texta);
1071 new_cell.set_text(texta);
1088 }
1072 }
1089 };
1073 }
1090 };
1074 };
1091
1075
1092 /**
1076 /**
1093 * Combine the selected cell into the cell above it.
1077 * Combine the selected cell into the cell above it.
1094 *
1078 *
1095 * @method merge_cell_above
1079 * @method merge_cell_above
1096 */
1080 */
1097 Notebook.prototype.merge_cell_above = function () {
1081 Notebook.prototype.merge_cell_above = function () {
1098 var mdc = IPython.MarkdownCell;
1082 var mdc = IPython.MarkdownCell;
1099 var rc = IPython.RawCell;
1083 var rc = IPython.RawCell;
1100 var index = this.get_selected_index();
1084 var index = this.get_selected_index();
1101 var cell = this.get_cell(index);
1085 var cell = this.get_cell(index);
1102 var render = cell.rendered;
1086 var render = cell.rendered;
1103 if (!cell.is_mergeable()) {
1087 if (!cell.is_mergeable()) {
1104 return;
1088 return;
1105 }
1089 }
1106 if (index > 0) {
1090 if (index > 0) {
1107 var upper_cell = this.get_cell(index-1);
1091 var upper_cell = this.get_cell(index-1);
1108 if (!upper_cell.is_mergeable()) {
1092 if (!upper_cell.is_mergeable()) {
1109 return;
1093 return;
1110 }
1094 }
1111 var upper_text = upper_cell.get_text();
1095 var upper_text = upper_cell.get_text();
1112 var text = cell.get_text();
1096 var text = cell.get_text();
1113 if (cell instanceof IPython.CodeCell) {
1097 if (cell instanceof IPython.CodeCell) {
1114 cell.set_text(upper_text+'\n'+text);
1098 cell.set_text(upper_text+'\n'+text);
1115 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1099 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1116 cell.unrender(); // Must unrender before we set_text.
1100 cell.unrender(); // Must unrender before we set_text.
1117 cell.set_text(upper_text+'\n\n'+text);
1101 cell.set_text(upper_text+'\n\n'+text);
1118 if (render) {
1102 if (render) {
1119 // The rendered state of the final cell should match
1103 // The rendered state of the final cell should match
1120 // that of the original selected cell;
1104 // that of the original selected cell;
1121 cell.render();
1105 cell.render();
1122 }
1106 }
1123 };
1107 }
1124 this.delete_cell(index-1);
1108 this.delete_cell(index-1);
1125 this.select(this.find_cell_index(cell));
1109 this.select(this.find_cell_index(cell));
1126 };
1110 }
1127 };
1111 };
1128
1112
1129 /**
1113 /**
1130 * Combine the selected cell into the cell below it.
1114 * Combine the selected cell into the cell below it.
1131 *
1115 *
1132 * @method merge_cell_below
1116 * @method merge_cell_below
1133 */
1117 */
1134 Notebook.prototype.merge_cell_below = function () {
1118 Notebook.prototype.merge_cell_below = function () {
1135 var mdc = IPython.MarkdownCell;
1119 var mdc = IPython.MarkdownCell;
1136 var rc = IPython.RawCell;
1120 var rc = IPython.RawCell;
1137 var index = this.get_selected_index();
1121 var index = this.get_selected_index();
1138 var cell = this.get_cell(index);
1122 var cell = this.get_cell(index);
1139 var render = cell.rendered;
1123 var render = cell.rendered;
1140 if (!cell.is_mergeable()) {
1124 if (!cell.is_mergeable()) {
1141 return;
1125 return;
1142 }
1126 }
1143 if (index < this.ncells()-1) {
1127 if (index < this.ncells()-1) {
1144 var lower_cell = this.get_cell(index+1);
1128 var lower_cell = this.get_cell(index+1);
1145 if (!lower_cell.is_mergeable()) {
1129 if (!lower_cell.is_mergeable()) {
1146 return;
1130 return;
1147 }
1131 }
1148 var lower_text = lower_cell.get_text();
1132 var lower_text = lower_cell.get_text();
1149 var text = cell.get_text();
1133 var text = cell.get_text();
1150 if (cell instanceof IPython.CodeCell) {
1134 if (cell instanceof IPython.CodeCell) {
1151 cell.set_text(text+'\n'+lower_text);
1135 cell.set_text(text+'\n'+lower_text);
1152 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1136 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1153 cell.unrender(); // Must unrender before we set_text.
1137 cell.unrender(); // Must unrender before we set_text.
1154 cell.set_text(text+'\n\n'+lower_text);
1138 cell.set_text(text+'\n\n'+lower_text);
1155 if (render) {
1139 if (render) {
1156 // The rendered state of the final cell should match
1140 // The rendered state of the final cell should match
1157 // that of the original selected cell;
1141 // that of the original selected cell;
1158 cell.render();
1142 cell.render();
1159 }
1143 }
1160 };
1144 }
1161 this.delete_cell(index+1);
1145 this.delete_cell(index+1);
1162 this.select(this.find_cell_index(cell));
1146 this.select(this.find_cell_index(cell));
1163 };
1147 }
1164 };
1148 };
1165
1149
1166
1150
1167 // Cell collapsing and output clearing
1151 // Cell collapsing and output clearing
1168
1152
1169 /**
1153 /**
1170 * Hide a cell's output.
1154 * Hide a cell's output.
1171 *
1155 *
1172 * @method collapse_output
1156 * @method collapse_output
1173 * @param {Number} index A cell's numeric index
1157 * @param {Number} index A cell's numeric index
1174 */
1158 */
1175 Notebook.prototype.collapse_output = function (index) {
1159 Notebook.prototype.collapse_output = function (index) {
1176 var i = this.index_or_selected(index);
1160 var i = this.index_or_selected(index);
1177 var cell = this.get_cell(i);
1161 var cell = this.get_cell(i);
1178 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1162 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1179 cell.collapse_output();
1163 cell.collapse_output();
1180 this.set_dirty(true);
1164 this.set_dirty(true);
1181 }
1165 }
1182 };
1166 };
1183
1167
1184 /**
1168 /**
1185 * Hide each code cell's output area.
1169 * Hide each code cell's output area.
1186 *
1170 *
1187 * @method collapse_all_output
1171 * @method collapse_all_output
1188 */
1172 */
1189 Notebook.prototype.collapse_all_output = function () {
1173 Notebook.prototype.collapse_all_output = function () {
1190 $.map(this.get_cells(), function (cell, i) {
1174 $.map(this.get_cells(), function (cell, i) {
1191 if (cell instanceof IPython.CodeCell) {
1175 if (cell instanceof IPython.CodeCell) {
1192 cell.collapse_output();
1176 cell.collapse_output();
1193 }
1177 }
1194 });
1178 });
1195 // this should not be set if the `collapse` key is removed from nbformat
1179 // this should not be set if the `collapse` key is removed from nbformat
1196 this.set_dirty(true);
1180 this.set_dirty(true);
1197 };
1181 };
1198
1182
1199 /**
1183 /**
1200 * Show a cell's output.
1184 * Show a cell's output.
1201 *
1185 *
1202 * @method expand_output
1186 * @method expand_output
1203 * @param {Number} index A cell's numeric index
1187 * @param {Number} index A cell's numeric index
1204 */
1188 */
1205 Notebook.prototype.expand_output = function (index) {
1189 Notebook.prototype.expand_output = function (index) {
1206 var i = this.index_or_selected(index);
1190 var i = this.index_or_selected(index);
1207 var cell = this.get_cell(i);
1191 var cell = this.get_cell(i);
1208 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1192 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1209 cell.expand_output();
1193 cell.expand_output();
1210 this.set_dirty(true);
1194 this.set_dirty(true);
1211 }
1195 }
1212 };
1196 };
1213
1197
1214 /**
1198 /**
1215 * Expand each code cell's output area, and remove scrollbars.
1199 * Expand each code cell's output area, and remove scrollbars.
1216 *
1200 *
1217 * @method expand_all_output
1201 * @method expand_all_output
1218 */
1202 */
1219 Notebook.prototype.expand_all_output = function () {
1203 Notebook.prototype.expand_all_output = function () {
1220 $.map(this.get_cells(), function (cell, i) {
1204 $.map(this.get_cells(), function (cell, i) {
1221 if (cell instanceof IPython.CodeCell) {
1205 if (cell instanceof IPython.CodeCell) {
1222 cell.expand_output();
1206 cell.expand_output();
1223 }
1207 }
1224 });
1208 });
1225 // this should not be set if the `collapse` key is removed from nbformat
1209 // this should not be set if the `collapse` key is removed from nbformat
1226 this.set_dirty(true);
1210 this.set_dirty(true);
1227 };
1211 };
1228
1212
1229 /**
1213 /**
1230 * Clear the selected CodeCell's output area.
1214 * Clear the selected CodeCell's output area.
1231 *
1215 *
1232 * @method clear_output
1216 * @method clear_output
1233 * @param {Number} index A cell's numeric index
1217 * @param {Number} index A cell's numeric index
1234 */
1218 */
1235 Notebook.prototype.clear_output = function (index) {
1219 Notebook.prototype.clear_output = function (index) {
1236 var i = this.index_or_selected(index);
1220 var i = this.index_or_selected(index);
1237 var cell = this.get_cell(i);
1221 var cell = this.get_cell(i);
1238 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1222 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1239 cell.clear_output();
1223 cell.clear_output();
1240 this.set_dirty(true);
1224 this.set_dirty(true);
1241 }
1225 }
1242 };
1226 };
1243
1227
1244 /**
1228 /**
1245 * Clear each code cell's output area.
1229 * Clear each code cell's output area.
1246 *
1230 *
1247 * @method clear_all_output
1231 * @method clear_all_output
1248 */
1232 */
1249 Notebook.prototype.clear_all_output = function () {
1233 Notebook.prototype.clear_all_output = function () {
1250 $.map(this.get_cells(), function (cell, i) {
1234 $.map(this.get_cells(), function (cell, i) {
1251 if (cell instanceof IPython.CodeCell) {
1235 if (cell instanceof IPython.CodeCell) {
1252 cell.clear_output();
1236 cell.clear_output();
1253 }
1237 }
1254 });
1238 });
1255 this.set_dirty(true);
1239 this.set_dirty(true);
1256 };
1240 };
1257
1241
1258 /**
1242 /**
1259 * Scroll the selected CodeCell's output area.
1243 * Scroll the selected CodeCell's output area.
1260 *
1244 *
1261 * @method scroll_output
1245 * @method scroll_output
1262 * @param {Number} index A cell's numeric index
1246 * @param {Number} index A cell's numeric index
1263 */
1247 */
1264 Notebook.prototype.scroll_output = function (index) {
1248 Notebook.prototype.scroll_output = function (index) {
1265 var i = this.index_or_selected(index);
1249 var i = this.index_or_selected(index);
1266 var cell = this.get_cell(i);
1250 var cell = this.get_cell(i);
1267 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1251 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1268 cell.scroll_output();
1252 cell.scroll_output();
1269 this.set_dirty(true);
1253 this.set_dirty(true);
1270 }
1254 }
1271 };
1255 };
1272
1256
1273 /**
1257 /**
1274 * Expand each code cell's output area, and add a scrollbar for long output.
1258 * Expand each code cell's output area, and add a scrollbar for long output.
1275 *
1259 *
1276 * @method scroll_all_output
1260 * @method scroll_all_output
1277 */
1261 */
1278 Notebook.prototype.scroll_all_output = function () {
1262 Notebook.prototype.scroll_all_output = function () {
1279 $.map(this.get_cells(), function (cell, i) {
1263 $.map(this.get_cells(), function (cell, i) {
1280 if (cell instanceof IPython.CodeCell) {
1264 if (cell instanceof IPython.CodeCell) {
1281 cell.scroll_output();
1265 cell.scroll_output();
1282 }
1266 }
1283 });
1267 });
1284 // this should not be set if the `collapse` key is removed from nbformat
1268 // this should not be set if the `collapse` key is removed from nbformat
1285 this.set_dirty(true);
1269 this.set_dirty(true);
1286 };
1270 };
1287
1271
1288 /** Toggle whether a cell's output is collapsed or expanded.
1272 /** Toggle whether a cell's output is collapsed or expanded.
1289 *
1273 *
1290 * @method toggle_output
1274 * @method toggle_output
1291 * @param {Number} index A cell's numeric index
1275 * @param {Number} index A cell's numeric index
1292 */
1276 */
1293 Notebook.prototype.toggle_output = function (index) {
1277 Notebook.prototype.toggle_output = function (index) {
1294 var i = this.index_or_selected(index);
1278 var i = this.index_or_selected(index);
1295 var cell = this.get_cell(i);
1279 var cell = this.get_cell(i);
1296 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1280 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1297 cell.toggle_output();
1281 cell.toggle_output();
1298 this.set_dirty(true);
1282 this.set_dirty(true);
1299 }
1283 }
1300 };
1284 };
1301
1285
1302 /**
1286 /**
1303 * Hide/show the output of all cells.
1287 * Hide/show the output of all cells.
1304 *
1288 *
1305 * @method toggle_all_output
1289 * @method toggle_all_output
1306 */
1290 */
1307 Notebook.prototype.toggle_all_output = function () {
1291 Notebook.prototype.toggle_all_output = function () {
1308 $.map(this.get_cells(), function (cell, i) {
1292 $.map(this.get_cells(), function (cell, i) {
1309 if (cell instanceof IPython.CodeCell) {
1293 if (cell instanceof IPython.CodeCell) {
1310 cell.toggle_output();
1294 cell.toggle_output();
1311 }
1295 }
1312 });
1296 });
1313 // this should not be set if the `collapse` key is removed from nbformat
1297 // this should not be set if the `collapse` key is removed from nbformat
1314 this.set_dirty(true);
1298 this.set_dirty(true);
1315 };
1299 };
1316
1300
1317 /**
1301 /**
1318 * Toggle a scrollbar for long cell outputs.
1302 * Toggle a scrollbar for long cell outputs.
1319 *
1303 *
1320 * @method toggle_output_scroll
1304 * @method toggle_output_scroll
1321 * @param {Number} index A cell's numeric index
1305 * @param {Number} index A cell's numeric index
1322 */
1306 */
1323 Notebook.prototype.toggle_output_scroll = function (index) {
1307 Notebook.prototype.toggle_output_scroll = function (index) {
1324 var i = this.index_or_selected(index);
1308 var i = this.index_or_selected(index);
1325 var cell = this.get_cell(i);
1309 var cell = this.get_cell(i);
1326 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1310 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1327 cell.toggle_output_scroll();
1311 cell.toggle_output_scroll();
1328 this.set_dirty(true);
1312 this.set_dirty(true);
1329 }
1313 }
1330 };
1314 };
1331
1315
1332 /**
1316 /**
1333 * Toggle the scrolling of long output on all cells.
1317 * Toggle the scrolling of long output on all cells.
1334 *
1318 *
1335 * @method toggle_all_output_scrolling
1319 * @method toggle_all_output_scrolling
1336 */
1320 */
1337 Notebook.prototype.toggle_all_output_scroll = function () {
1321 Notebook.prototype.toggle_all_output_scroll = function () {
1338 $.map(this.get_cells(), function (cell, i) {
1322 $.map(this.get_cells(), function (cell, i) {
1339 if (cell instanceof IPython.CodeCell) {
1323 if (cell instanceof IPython.CodeCell) {
1340 cell.toggle_output_scroll();
1324 cell.toggle_output_scroll();
1341 }
1325 }
1342 });
1326 });
1343 // this should not be set if the `collapse` key is removed from nbformat
1327 // this should not be set if the `collapse` key is removed from nbformat
1344 this.set_dirty(true);
1328 this.set_dirty(true);
1345 };
1329 };
1346
1330
1347 // Other cell functions: line numbers, ...
1331 // Other cell functions: line numbers, ...
1348
1332
1349 /**
1333 /**
1350 * Toggle line numbers in the selected cell's input area.
1334 * Toggle line numbers in the selected cell's input area.
1351 *
1335 *
1352 * @method cell_toggle_line_numbers
1336 * @method cell_toggle_line_numbers
1353 */
1337 */
1354 Notebook.prototype.cell_toggle_line_numbers = function() {
1338 Notebook.prototype.cell_toggle_line_numbers = function() {
1355 this.get_selected_cell().toggle_line_numbers();
1339 this.get_selected_cell().toggle_line_numbers();
1356 };
1340 };
1357
1341
1358 // Session related things
1342 // Session related things
1359
1343
1360 /**
1344 /**
1361 * Start a new session and set it on each code cell.
1345 * Start a new session and set it on each code cell.
1362 *
1346 *
1363 * @method start_session
1347 * @method start_session
1364 */
1348 */
1365 Notebook.prototype.start_session = function () {
1349 Notebook.prototype.start_session = function () {
1366 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1350 this.session = new IPython.Session(this, this.options);
1367 this.session.start($.proxy(this._session_started, this));
1351 this.session.start($.proxy(this._session_started, this));
1368 };
1352 };
1369
1353
1370
1354
1371 /**
1355 /**
1372 * Once a session is started, link the code cells to the kernel and pass the
1356 * Once a session is started, link the code cells to the kernel and pass the
1373 * comm manager to the widget manager
1357 * comm manager to the widget manager
1374 *
1358 *
1375 */
1359 */
1376 Notebook.prototype._session_started = function(){
1360 Notebook.prototype._session_started = function(){
1377 this.kernel = this.session.kernel;
1361 this.kernel = this.session.kernel;
1378 var ncells = this.ncells();
1362 var ncells = this.ncells();
1379 for (var i=0; i<ncells; i++) {
1363 for (var i=0; i<ncells; i++) {
1380 var cell = this.get_cell(i);
1364 var cell = this.get_cell(i);
1381 if (cell instanceof IPython.CodeCell) {
1365 if (cell instanceof IPython.CodeCell) {
1382 cell.set_kernel(this.session.kernel);
1366 cell.set_kernel(this.session.kernel);
1383 };
1367 }
1384 };
1368 }
1385 };
1369 };
1386
1370
1387 /**
1371 /**
1388 * Prompt the user to restart the IPython kernel.
1372 * Prompt the user to restart the IPython kernel.
1389 *
1373 *
1390 * @method restart_kernel
1374 * @method restart_kernel
1391 */
1375 */
1392 Notebook.prototype.restart_kernel = function () {
1376 Notebook.prototype.restart_kernel = function () {
1393 var that = this;
1377 var that = this;
1394 IPython.dialog.modal({
1378 IPython.dialog.modal({
1395 title : "Restart kernel or continue running?",
1379 title : "Restart kernel or continue running?",
1396 body : $("<p/>").text(
1380 body : $("<p/>").text(
1397 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1381 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1398 ),
1382 ),
1399 buttons : {
1383 buttons : {
1400 "Continue running" : {},
1384 "Continue running" : {},
1401 "Restart" : {
1385 "Restart" : {
1402 "class" : "btn-danger",
1386 "class" : "btn-danger",
1403 "click" : function() {
1387 "click" : function() {
1404 that.session.restart_kernel();
1388 that.session.restart_kernel();
1405 }
1389 }
1406 }
1390 }
1407 }
1391 }
1408 });
1392 });
1409 };
1393 };
1410
1394
1411 /**
1395 /**
1412 * Execute or render cell outputs and go into command mode.
1396 * Execute or render cell outputs and go into command mode.
1413 *
1397 *
1414 * @method execute_cell
1398 * @method execute_cell
1415 */
1399 */
1416 Notebook.prototype.execute_cell = function () {
1400 Notebook.prototype.execute_cell = function () {
1417 // mode = shift, ctrl, alt
1401 // mode = shift, ctrl, alt
1418 var cell = this.get_selected_cell();
1402 var cell = this.get_selected_cell();
1419 var cell_index = this.find_cell_index(cell);
1403 var cell_index = this.find_cell_index(cell);
1420
1404
1421 cell.execute();
1405 cell.execute();
1422 this.command_mode();
1406 this.command_mode();
1423 cell.focus_cell();
1407 cell.focus_cell();
1424 this.set_dirty(true);
1408 this.set_dirty(true);
1425 }
1409 };
1426
1410
1427 /**
1411 /**
1428 * Execute or render cell outputs and insert a new cell below.
1412 * Execute or render cell outputs and insert a new cell below.
1429 *
1413 *
1430 * @method execute_cell_and_insert_below
1414 * @method execute_cell_and_insert_below
1431 */
1415 */
1432 Notebook.prototype.execute_cell_and_insert_below = function () {
1416 Notebook.prototype.execute_cell_and_insert_below = function () {
1433 var cell = this.get_selected_cell();
1417 var cell = this.get_selected_cell();
1434 var cell_index = this.find_cell_index(cell);
1418 var cell_index = this.find_cell_index(cell);
1435
1419
1436 cell.execute();
1420 cell.execute();
1437
1421
1438 // If we are at the end always insert a new cell and return
1422 // If we are at the end always insert a new cell and return
1439 if (cell_index === (this.ncells()-1)) {
1423 if (cell_index === (this.ncells()-1)) {
1440 this.insert_cell_below('code');
1424 this.insert_cell_below('code');
1441 this.select(cell_index+1);
1425 this.select(cell_index+1);
1442 this.edit_mode();
1426 this.edit_mode();
1443 this.scroll_to_bottom();
1427 this.scroll_to_bottom();
1444 this.set_dirty(true);
1428 this.set_dirty(true);
1445 return;
1429 return;
1446 }
1430 }
1447
1431
1448 this.insert_cell_below('code');
1432 this.insert_cell_below('code');
1449 this.select(cell_index+1);
1433 this.select(cell_index+1);
1450 this.edit_mode();
1434 this.edit_mode();
1451 this.set_dirty(true);
1435 this.set_dirty(true);
1452 };
1436 };
1453
1437
1454 /**
1438 /**
1455 * Execute or render cell outputs and select the next cell.
1439 * Execute or render cell outputs and select the next cell.
1456 *
1440 *
1457 * @method execute_cell_and_select_below
1441 * @method execute_cell_and_select_below
1458 */
1442 */
1459 Notebook.prototype.execute_cell_and_select_below = function () {
1443 Notebook.prototype.execute_cell_and_select_below = function () {
1460
1444
1461 var cell = this.get_selected_cell();
1445 var cell = this.get_selected_cell();
1462 var cell_index = this.find_cell_index(cell);
1446 var cell_index = this.find_cell_index(cell);
1463
1447
1464 cell.execute();
1448 cell.execute();
1465
1449
1466 // If we are at the end always insert a new cell and return
1450 // If we are at the end always insert a new cell and return
1467 if (cell_index === (this.ncells()-1)) {
1451 if (cell_index === (this.ncells()-1)) {
1468 this.insert_cell_below('code');
1452 this.insert_cell_below('code');
1469 this.select(cell_index+1);
1453 this.select(cell_index+1);
1470 this.edit_mode();
1454 this.edit_mode();
1471 this.scroll_to_bottom();
1455 this.scroll_to_bottom();
1472 this.set_dirty(true);
1456 this.set_dirty(true);
1473 return;
1457 return;
1474 }
1458 }
1475
1459
1476 this.select(cell_index+1);
1460 this.select(cell_index+1);
1477 this.get_cell(cell_index+1).focus_cell();
1461 this.get_cell(cell_index+1).focus_cell();
1478 this.set_dirty(true);
1462 this.set_dirty(true);
1479 };
1463 };
1480
1464
1481 /**
1465 /**
1482 * Execute all cells below the selected cell.
1466 * Execute all cells below the selected cell.
1483 *
1467 *
1484 * @method execute_cells_below
1468 * @method execute_cells_below
1485 */
1469 */
1486 Notebook.prototype.execute_cells_below = function () {
1470 Notebook.prototype.execute_cells_below = function () {
1487 this.execute_cell_range(this.get_selected_index(), this.ncells());
1471 this.execute_cell_range(this.get_selected_index(), this.ncells());
1488 this.scroll_to_bottom();
1472 this.scroll_to_bottom();
1489 };
1473 };
1490
1474
1491 /**
1475 /**
1492 * Execute all cells above the selected cell.
1476 * Execute all cells above the selected cell.
1493 *
1477 *
1494 * @method execute_cells_above
1478 * @method execute_cells_above
1495 */
1479 */
1496 Notebook.prototype.execute_cells_above = function () {
1480 Notebook.prototype.execute_cells_above = function () {
1497 this.execute_cell_range(0, this.get_selected_index());
1481 this.execute_cell_range(0, this.get_selected_index());
1498 };
1482 };
1499
1483
1500 /**
1484 /**
1501 * Execute all cells.
1485 * Execute all cells.
1502 *
1486 *
1503 * @method execute_all_cells
1487 * @method execute_all_cells
1504 */
1488 */
1505 Notebook.prototype.execute_all_cells = function () {
1489 Notebook.prototype.execute_all_cells = function () {
1506 this.execute_cell_range(0, this.ncells());
1490 this.execute_cell_range(0, this.ncells());
1507 this.scroll_to_bottom();
1491 this.scroll_to_bottom();
1508 };
1492 };
1509
1493
1510 /**
1494 /**
1511 * Execute a contiguous range of cells.
1495 * Execute a contiguous range of cells.
1512 *
1496 *
1513 * @method execute_cell_range
1497 * @method execute_cell_range
1514 * @param {Number} start Index of the first cell to execute (inclusive)
1498 * @param {Number} start Index of the first cell to execute (inclusive)
1515 * @param {Number} end Index of the last cell to execute (exclusive)
1499 * @param {Number} end Index of the last cell to execute (exclusive)
1516 */
1500 */
1517 Notebook.prototype.execute_cell_range = function (start, end) {
1501 Notebook.prototype.execute_cell_range = function (start, end) {
1518 for (var i=start; i<end; i++) {
1502 for (var i=start; i<end; i++) {
1519 this.select(i);
1503 this.select(i);
1520 this.execute_cell();
1504 this.execute_cell();
1521 };
1505 }
1522 };
1506 };
1523
1507
1524 // Persistance and loading
1508 // Persistance and loading
1525
1509
1526 /**
1510 /**
1527 * Getter method for this notebook's name.
1511 * Getter method for this notebook's name.
1528 *
1512 *
1529 * @method get_notebook_name
1513 * @method get_notebook_name
1530 * @return {String} This notebook's name
1514 * @return {String} This notebook's name (excluding file extension)
1531 */
1515 */
1532 Notebook.prototype.get_notebook_name = function () {
1516 Notebook.prototype.get_notebook_name = function () {
1533 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1517 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1534 return nbname;
1518 return nbname;
1535 };
1519 };
1536
1520
1537 /**
1521 /**
1538 * Setter method for this notebook's name.
1522 * Setter method for this notebook's name.
1539 *
1523 *
1540 * @method set_notebook_name
1524 * @method set_notebook_name
1541 * @param {String} name A new name for this notebook
1525 * @param {String} name A new name for this notebook
1542 */
1526 */
1543 Notebook.prototype.set_notebook_name = function (name) {
1527 Notebook.prototype.set_notebook_name = function (name) {
1544 this.notebook_name = name;
1528 this.notebook_name = name;
1545 };
1529 };
1546
1530
1547 /**
1531 /**
1548 * Check that a notebook's name is valid.
1532 * Check that a notebook's name is valid.
1549 *
1533 *
1550 * @method test_notebook_name
1534 * @method test_notebook_name
1551 * @param {String} nbname A name for this notebook
1535 * @param {String} nbname A name for this notebook
1552 * @return {Boolean} True if the name is valid, false if invalid
1536 * @return {Boolean} True if the name is valid, false if invalid
1553 */
1537 */
1554 Notebook.prototype.test_notebook_name = function (nbname) {
1538 Notebook.prototype.test_notebook_name = function (nbname) {
1555 nbname = nbname || '';
1539 nbname = nbname || '';
1556 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1540 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1557 return true;
1541 return true;
1558 } else {
1542 } else {
1559 return false;
1543 return false;
1560 };
1544 }
1561 };
1545 };
1562
1546
1563 /**
1547 /**
1564 * Load a notebook from JSON (.ipynb).
1548 * Load a notebook from JSON (.ipynb).
1565 *
1549 *
1566 * This currently handles one worksheet: others are deleted.
1550 * This currently handles one worksheet: others are deleted.
1567 *
1551 *
1568 * @method fromJSON
1552 * @method fromJSON
1569 * @param {Object} data JSON representation of a notebook
1553 * @param {Object} data JSON representation of a notebook
1570 */
1554 */
1571 Notebook.prototype.fromJSON = function (data) {
1555 Notebook.prototype.fromJSON = function (data) {
1572 var content = data.content;
1556 var content = data.content;
1573 var ncells = this.ncells();
1557 var ncells = this.ncells();
1574 var i;
1558 var i;
1575 for (i=0; i<ncells; i++) {
1559 for (i=0; i<ncells; i++) {
1576 // Always delete cell 0 as they get renumbered as they are deleted.
1560 // Always delete cell 0 as they get renumbered as they are deleted.
1577 this.delete_cell(0);
1561 this.delete_cell(0);
1578 };
1562 }
1579 // Save the metadata and name.
1563 // Save the metadata and name.
1580 this.metadata = content.metadata;
1564 this.metadata = content.metadata;
1581 this.notebook_name = data.name;
1565 this.notebook_name = data.name;
1582 // Only handle 1 worksheet for now.
1566 // Only handle 1 worksheet for now.
1583 var worksheet = content.worksheets[0];
1567 var worksheet = content.worksheets[0];
1584 if (worksheet !== undefined) {
1568 if (worksheet !== undefined) {
1585 if (worksheet.metadata) {
1569 if (worksheet.metadata) {
1586 this.worksheet_metadata = worksheet.metadata;
1570 this.worksheet_metadata = worksheet.metadata;
1587 }
1571 }
1588 var new_cells = worksheet.cells;
1572 var new_cells = worksheet.cells;
1589 ncells = new_cells.length;
1573 ncells = new_cells.length;
1590 var cell_data = null;
1574 var cell_data = null;
1591 var new_cell = null;
1575 var new_cell = null;
1592 for (i=0; i<ncells; i++) {
1576 for (i=0; i<ncells; i++) {
1593 cell_data = new_cells[i];
1577 cell_data = new_cells[i];
1594 // VERSIONHACK: plaintext -> raw
1578 // VERSIONHACK: plaintext -> raw
1595 // handle never-released plaintext name for raw cells
1579 // handle never-released plaintext name for raw cells
1596 if (cell_data.cell_type === 'plaintext'){
1580 if (cell_data.cell_type === 'plaintext'){
1597 cell_data.cell_type = 'raw';
1581 cell_data.cell_type = 'raw';
1598 }
1582 }
1599
1583
1600 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1584 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1601 new_cell.fromJSON(cell_data);
1585 new_cell.fromJSON(cell_data);
1602 };
1586 }
1603 };
1587 }
1604 if (content.worksheets.length > 1) {
1588 if (content.worksheets.length > 1) {
1605 IPython.dialog.modal({
1589 IPython.dialog.modal({
1606 title : "Multiple worksheets",
1590 title : "Multiple worksheets",
1607 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1591 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1608 "but this version of IPython can only handle the first. " +
1592 "but this version of IPython can only handle the first. " +
1609 "If you save this notebook, worksheets after the first will be lost.",
1593 "If you save this notebook, worksheets after the first will be lost.",
1610 buttons : {
1594 buttons : {
1611 OK : {
1595 OK : {
1612 class : "btn-danger"
1596 class : "btn-danger"
1613 }
1597 }
1614 }
1598 }
1615 });
1599 });
1616 }
1600 }
1617 };
1601 };
1618
1602
1619 /**
1603 /**
1620 * Dump this notebook into a JSON-friendly object.
1604 * Dump this notebook into a JSON-friendly object.
1621 *
1605 *
1622 * @method toJSON
1606 * @method toJSON
1623 * @return {Object} A JSON-friendly representation of this notebook.
1607 * @return {Object} A JSON-friendly representation of this notebook.
1624 */
1608 */
1625 Notebook.prototype.toJSON = function () {
1609 Notebook.prototype.toJSON = function () {
1626 var cells = this.get_cells();
1610 var cells = this.get_cells();
1627 var ncells = cells.length;
1611 var ncells = cells.length;
1628 var cell_array = new Array(ncells);
1612 var cell_array = new Array(ncells);
1629 for (var i=0; i<ncells; i++) {
1613 for (var i=0; i<ncells; i++) {
1630 cell_array[i] = cells[i].toJSON();
1614 cell_array[i] = cells[i].toJSON();
1631 };
1615 }
1632 var data = {
1616 var data = {
1633 // Only handle 1 worksheet for now.
1617 // Only handle 1 worksheet for now.
1634 worksheets : [{
1618 worksheets : [{
1635 cells: cell_array,
1619 cells: cell_array,
1636 metadata: this.worksheet_metadata
1620 metadata: this.worksheet_metadata
1637 }],
1621 }],
1638 metadata : this.metadata
1622 metadata : this.metadata
1639 };
1623 };
1640 return data;
1624 return data;
1641 };
1625 };
1642
1626
1643 /**
1627 /**
1644 * Start an autosave timer, for periodically saving the notebook.
1628 * Start an autosave timer, for periodically saving the notebook.
1645 *
1629 *
1646 * @method set_autosave_interval
1630 * @method set_autosave_interval
1647 * @param {Integer} interval the autosave interval in milliseconds
1631 * @param {Integer} interval the autosave interval in milliseconds
1648 */
1632 */
1649 Notebook.prototype.set_autosave_interval = function (interval) {
1633 Notebook.prototype.set_autosave_interval = function (interval) {
1650 var that = this;
1634 var that = this;
1651 // clear previous interval, so we don't get simultaneous timers
1635 // clear previous interval, so we don't get simultaneous timers
1652 if (this.autosave_timer) {
1636 if (this.autosave_timer) {
1653 clearInterval(this.autosave_timer);
1637 clearInterval(this.autosave_timer);
1654 }
1638 }
1655
1639
1656 this.autosave_interval = this.minimum_autosave_interval = interval;
1640 this.autosave_interval = this.minimum_autosave_interval = interval;
1657 if (interval) {
1641 if (interval) {
1658 this.autosave_timer = setInterval(function() {
1642 this.autosave_timer = setInterval(function() {
1659 if (that.dirty) {
1643 if (that.dirty) {
1660 that.save_notebook();
1644 that.save_notebook();
1661 }
1645 }
1662 }, interval);
1646 }, interval);
1663 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1647 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1664 } else {
1648 } else {
1665 this.autosave_timer = null;
1649 this.autosave_timer = null;
1666 $([IPython.events]).trigger("autosave_disabled.Notebook");
1650 $([IPython.events]).trigger("autosave_disabled.Notebook");
1667 };
1651 }
1668 };
1652 };
1669
1653
1670 /**
1654 /**
1671 * Save this notebook on the server.
1655 * Save this notebook on the server.
1672 *
1656 *
1673 * @method save_notebook
1657 * @method save_notebook
1674 */
1658 */
1675 Notebook.prototype.save_notebook = function (extra_settings) {
1659 Notebook.prototype.save_notebook = function (extra_settings) {
1676 // Create a JSON model to be sent to the server.
1660 // Create a JSON model to be sent to the server.
1677 var model = {};
1661 var model = {};
1678 model.name = this.notebook_name;
1662 model.name = this.notebook_name;
1679 model.path = this.notebook_path;
1663 model.path = this.notebook_path;
1680 model.content = this.toJSON();
1664 model.content = this.toJSON();
1681 model.content.nbformat = this.nbformat;
1665 model.content.nbformat = this.nbformat;
1682 model.content.nbformat_minor = this.nbformat_minor;
1666 model.content.nbformat_minor = this.nbformat_minor;
1683 // time the ajax call for autosave tuning purposes.
1667 // time the ajax call for autosave tuning purposes.
1684 var start = new Date().getTime();
1668 var start = new Date().getTime();
1685 // We do the call with settings so we can set cache to false.
1669 // We do the call with settings so we can set cache to false.
1686 var settings = {
1670 var settings = {
1687 processData : false,
1671 processData : false,
1688 cache : false,
1672 cache : false,
1689 type : "PUT",
1673 type : "PUT",
1690 data : JSON.stringify(model),
1674 data : JSON.stringify(model),
1691 headers : {'Content-Type': 'application/json'},
1675 headers : {'Content-Type': 'application/json'},
1692 success : $.proxy(this.save_notebook_success, this, start),
1676 success : $.proxy(this.save_notebook_success, this, start),
1693 error : $.proxy(this.save_notebook_error, this)
1677 error : $.proxy(this.save_notebook_error, this)
1694 };
1678 };
1695 if (extra_settings) {
1679 if (extra_settings) {
1696 for (var key in extra_settings) {
1680 for (var key in extra_settings) {
1697 settings[key] = extra_settings[key];
1681 settings[key] = extra_settings[key];
1698 }
1682 }
1699 }
1683 }
1700 $([IPython.events]).trigger('notebook_saving.Notebook');
1684 $([IPython.events]).trigger('notebook_saving.Notebook');
1701 var url = utils.url_join_encode(
1685 var url = utils.url_join_encode(
1702 this._baseProjectUrl,
1686 this.base_url,
1703 'api/notebooks',
1687 'api/notebooks',
1704 this.notebook_path,
1688 this.notebook_path,
1705 this.notebook_name
1689 this.notebook_name
1706 );
1690 );
1707 $.ajax(url, settings);
1691 $.ajax(url, settings);
1708 };
1692 };
1709
1693
1710 /**
1694 /**
1711 * Success callback for saving a notebook.
1695 * Success callback for saving a notebook.
1712 *
1696 *
1713 * @method save_notebook_success
1697 * @method save_notebook_success
1714 * @param {Integer} start the time when the save request started
1698 * @param {Integer} start the time when the save request started
1715 * @param {Object} data JSON representation of a notebook
1699 * @param {Object} data JSON representation of a notebook
1716 * @param {String} status Description of response status
1700 * @param {String} status Description of response status
1717 * @param {jqXHR} xhr jQuery Ajax object
1701 * @param {jqXHR} xhr jQuery Ajax object
1718 */
1702 */
1719 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1703 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1720 this.set_dirty(false);
1704 this.set_dirty(false);
1721 $([IPython.events]).trigger('notebook_saved.Notebook');
1705 $([IPython.events]).trigger('notebook_saved.Notebook');
1722 this._update_autosave_interval(start);
1706 this._update_autosave_interval(start);
1723 if (this._checkpoint_after_save) {
1707 if (this._checkpoint_after_save) {
1724 this.create_checkpoint();
1708 this.create_checkpoint();
1725 this._checkpoint_after_save = false;
1709 this._checkpoint_after_save = false;
1726 };
1710 }
1727 };
1711 };
1728
1712
1729 /**
1713 /**
1730 * update the autosave interval based on how long the last save took
1714 * update the autosave interval based on how long the last save took
1731 *
1715 *
1732 * @method _update_autosave_interval
1716 * @method _update_autosave_interval
1733 * @param {Integer} timestamp when the save request started
1717 * @param {Integer} timestamp when the save request started
1734 */
1718 */
1735 Notebook.prototype._update_autosave_interval = function (start) {
1719 Notebook.prototype._update_autosave_interval = function (start) {
1736 var duration = (new Date().getTime() - start);
1720 var duration = (new Date().getTime() - start);
1737 if (this.autosave_interval) {
1721 if (this.autosave_interval) {
1738 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1722 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1739 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1723 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1740 // round to 10 seconds, otherwise we will be setting a new interval too often
1724 // round to 10 seconds, otherwise we will be setting a new interval too often
1741 interval = 10000 * Math.round(interval / 10000);
1725 interval = 10000 * Math.round(interval / 10000);
1742 // set new interval, if it's changed
1726 // set new interval, if it's changed
1743 if (interval != this.autosave_interval) {
1727 if (interval != this.autosave_interval) {
1744 this.set_autosave_interval(interval);
1728 this.set_autosave_interval(interval);
1745 }
1729 }
1746 }
1730 }
1747 };
1731 };
1748
1732
1749 /**
1733 /**
1750 * Failure callback for saving a notebook.
1734 * Failure callback for saving a notebook.
1751 *
1735 *
1752 * @method save_notebook_error
1736 * @method save_notebook_error
1753 * @param {jqXHR} xhr jQuery Ajax object
1737 * @param {jqXHR} xhr jQuery Ajax object
1754 * @param {String} status Description of response status
1738 * @param {String} status Description of response status
1755 * @param {String} error HTTP error message
1739 * @param {String} error HTTP error message
1756 */
1740 */
1757 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1741 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1758 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1742 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1759 };
1743 };
1760
1744
1761 Notebook.prototype.new_notebook = function(){
1745 Notebook.prototype.new_notebook = function(){
1762 var path = this.notebook_path;
1746 var path = this.notebook_path;
1763 var base_project_url = this._baseProjectUrl;
1747 var base_url = this.base_url;
1764 var settings = {
1748 var settings = {
1765 processData : false,
1749 processData : false,
1766 cache : false,
1750 cache : false,
1767 type : "POST",
1751 type : "POST",
1768 dataType : "json",
1752 dataType : "json",
1769 async : false,
1753 async : false,
1770 success : function (data, status, xhr){
1754 success : function (data, status, xhr){
1771 var notebook_name = data.name;
1755 var notebook_name = data.name;
1772 window.open(
1756 window.open(
1773 utils.url_join_encode(
1757 utils.url_join_encode(
1774 base_project_url,
1758 base_url,
1775 'notebooks',
1759 'notebooks',
1776 path,
1760 path,
1777 notebook_name
1761 notebook_name
1778 ),
1762 ),
1779 '_blank'
1763 '_blank'
1780 );
1764 );
1781 }
1765 }
1782 };
1766 };
1783 var url = utils.url_join_encode(
1767 var url = utils.url_join_encode(
1784 base_project_url,
1768 base_url,
1785 'api/notebooks',
1769 'api/notebooks',
1786 path
1770 path
1787 );
1771 );
1788 $.ajax(url,settings);
1772 $.ajax(url,settings);
1789 };
1773 };
1790
1774
1791
1775
1792 Notebook.prototype.copy_notebook = function(){
1776 Notebook.prototype.copy_notebook = function(){
1793 var path = this.notebook_path;
1777 var path = this.notebook_path;
1794 var base_project_url = this._baseProjectUrl;
1778 var base_url = this.base_url;
1795 var settings = {
1779 var settings = {
1796 processData : false,
1780 processData : false,
1797 cache : false,
1781 cache : false,
1798 type : "POST",
1782 type : "POST",
1799 dataType : "json",
1783 dataType : "json",
1800 data : JSON.stringify({copy_from : this.notebook_name}),
1784 data : JSON.stringify({copy_from : this.notebook_name}),
1801 async : false,
1785 async : false,
1802 success : function (data, status, xhr) {
1786 success : function (data, status, xhr) {
1803 window.open(utils.url_join_encode(
1787 window.open(utils.url_join_encode(
1804 base_project_url,
1788 base_url,
1805 'notebooks',
1789 'notebooks',
1806 data.path,
1790 data.path,
1807 data.name
1791 data.name
1808 ), '_blank');
1792 ), '_blank');
1809 }
1793 }
1810 };
1794 };
1811 var url = utils.url_join_encode(
1795 var url = utils.url_join_encode(
1812 base_project_url,
1796 base_url,
1813 'api/notebooks',
1797 'api/notebooks',
1814 path
1798 path
1815 );
1799 );
1816 $.ajax(url,settings);
1800 $.ajax(url,settings);
1817 };
1801 };
1818
1802
1819 Notebook.prototype.rename = function (nbname) {
1803 Notebook.prototype.rename = function (nbname) {
1820 var that = this;
1804 var that = this;
1821 var data = {name: nbname + '.ipynb'};
1805 if (!nbname.match(/\.ipynb$/)) {
1806 nbname = nbname + ".ipynb";
1807 }
1808 var data = {name: nbname};
1822 var settings = {
1809 var settings = {
1823 processData : false,
1810 processData : false,
1824 cache : false,
1811 cache : false,
1825 type : "PATCH",
1812 type : "PATCH",
1826 data : JSON.stringify(data),
1813 data : JSON.stringify(data),
1827 dataType: "json",
1814 dataType: "json",
1828 headers : {'Content-Type': 'application/json'},
1815 headers : {'Content-Type': 'application/json'},
1829 success : $.proxy(that.rename_success, this),
1816 success : $.proxy(that.rename_success, this),
1830 error : $.proxy(that.rename_error, this)
1817 error : $.proxy(that.rename_error, this)
1831 };
1818 };
1832 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1819 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1833 var url = utils.url_join_encode(
1820 var url = utils.url_join_encode(
1834 this._baseProjectUrl,
1821 this.base_url,
1835 'api/notebooks',
1822 'api/notebooks',
1836 this.notebook_path,
1823 this.notebook_path,
1837 this.notebook_name
1824 this.notebook_name
1838 );
1825 );
1839 $.ajax(url, settings);
1826 $.ajax(url, settings);
1840 };
1827 };
1841
1828
1842 Notebook.prototype.delete = function () {
1829 Notebook.prototype.delete = function () {
1843 var that = this;
1830 var that = this;
1844 var settings = {
1831 var settings = {
1845 processData : false,
1832 processData : false,
1846 cache : false,
1833 cache : false,
1847 type : "DELETE",
1834 type : "DELETE",
1848 dataType: "json",
1835 dataType: "json",
1849 };
1836 };
1850 var url = utils.url_join_encode(
1837 var url = utils.url_join_encode(
1851 this._baseProjectUrl,
1838 this.base_url,
1852 'api/notebooks',
1839 'api/notebooks',
1853 this.notebook_path,
1840 this.notebook_path,
1854 this.notebook_name
1841 this.notebook_name
1855 );
1842 );
1856 $.ajax(url, settings);
1843 $.ajax(url, settings);
1857 };
1844 };
1858
1845
1859
1846
1860 Notebook.prototype.rename_success = function (json, status, xhr) {
1847 Notebook.prototype.rename_success = function (json, status, xhr) {
1861 this.notebook_name = json.name;
1848 var name = this.notebook_name = json.name;
1862 var name = this.notebook_name;
1863 var path = json.path;
1849 var path = json.path;
1864 this.session.rename_notebook(name, path);
1850 this.session.rename_notebook(name, path);
1865 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1851 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1866 }
1852 };
1867
1853
1868 Notebook.prototype.rename_error = function (xhr, status, error) {
1854 Notebook.prototype.rename_error = function (xhr, status, error) {
1869 var that = this;
1855 var that = this;
1870 var dialog = $('<div/>').append(
1856 var dialog = $('<div/>').append(
1871 $("<p/>").addClass("rename-message")
1857 $("<p/>").addClass("rename-message")
1872 .text('This notebook name already exists.')
1858 .text('This notebook name already exists.')
1873 )
1859 );
1874 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1860 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1875 IPython.dialog.modal({
1861 IPython.dialog.modal({
1876 title: "Notebook Rename Error!",
1862 title: "Notebook Rename Error!",
1877 body: dialog,
1863 body: dialog,
1878 buttons : {
1864 buttons : {
1879 "Cancel": {},
1865 "Cancel": {},
1880 "OK": {
1866 "OK": {
1881 class: "btn-primary",
1867 class: "btn-primary",
1882 click: function () {
1868 click: function () {
1883 IPython.save_widget.rename_notebook();
1869 IPython.save_widget.rename_notebook();
1884 }}
1870 }}
1885 },
1871 },
1886 open : function (event, ui) {
1872 open : function (event, ui) {
1887 var that = $(this);
1873 var that = $(this);
1888 // Upon ENTER, click the OK button.
1874 // Upon ENTER, click the OK button.
1889 that.find('input[type="text"]').keydown(function (event, ui) {
1875 that.find('input[type="text"]').keydown(function (event, ui) {
1890 if (event.which === utils.keycodes.ENTER) {
1876 if (event.which === utils.keycodes.ENTER) {
1891 that.find('.btn-primary').first().click();
1877 that.find('.btn-primary').first().click();
1892 }
1878 }
1893 });
1879 });
1894 that.find('input[type="text"]').focus();
1880 that.find('input[type="text"]').focus();
1895 }
1881 }
1896 });
1882 });
1897 }
1883 };
1898
1884
1899 /**
1885 /**
1900 * Request a notebook's data from the server.
1886 * Request a notebook's data from the server.
1901 *
1887 *
1902 * @method load_notebook
1888 * @method load_notebook
1903 * @param {String} notebook_name and path A notebook to load
1889 * @param {String} notebook_name and path A notebook to load
1904 */
1890 */
1905 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1891 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1906 var that = this;
1892 var that = this;
1907 this.notebook_name = notebook_name;
1893 this.notebook_name = notebook_name;
1908 this.notebook_path = notebook_path;
1894 this.notebook_path = notebook_path;
1909 // We do the call with settings so we can set cache to false.
1895 // We do the call with settings so we can set cache to false.
1910 var settings = {
1896 var settings = {
1911 processData : false,
1897 processData : false,
1912 cache : false,
1898 cache : false,
1913 type : "GET",
1899 type : "GET",
1914 dataType : "json",
1900 dataType : "json",
1915 success : $.proxy(this.load_notebook_success,this),
1901 success : $.proxy(this.load_notebook_success,this),
1916 error : $.proxy(this.load_notebook_error,this),
1902 error : $.proxy(this.load_notebook_error,this),
1917 };
1903 };
1918 $([IPython.events]).trigger('notebook_loading.Notebook');
1904 $([IPython.events]).trigger('notebook_loading.Notebook');
1919 var url = utils.url_join_encode(
1905 var url = utils.url_join_encode(
1920 this._baseProjectUrl,
1906 this.base_url,
1921 'api/notebooks',
1907 'api/notebooks',
1922 this.notebook_path,
1908 this.notebook_path,
1923 this.notebook_name
1909 this.notebook_name
1924 );
1910 );
1925 $.ajax(url, settings);
1911 $.ajax(url, settings);
1926 };
1912 };
1927
1913
1928 /**
1914 /**
1929 * Success callback for loading a notebook from the server.
1915 * Success callback for loading a notebook from the server.
1930 *
1916 *
1931 * Load notebook data from the JSON response.
1917 * Load notebook data from the JSON response.
1932 *
1918 *
1933 * @method load_notebook_success
1919 * @method load_notebook_success
1934 * @param {Object} data JSON representation of a notebook
1920 * @param {Object} data JSON representation of a notebook
1935 * @param {String} status Description of response status
1921 * @param {String} status Description of response status
1936 * @param {jqXHR} xhr jQuery Ajax object
1922 * @param {jqXHR} xhr jQuery Ajax object
1937 */
1923 */
1938 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1924 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1939 this.fromJSON(data);
1925 this.fromJSON(data);
1940 if (this.ncells() === 0) {
1926 if (this.ncells() === 0) {
1941 this.insert_cell_below('code');
1927 this.insert_cell_below('code');
1942 this.select(0);
1928 this.select(0);
1943 this.edit_mode();
1929 this.edit_mode();
1944 } else {
1930 } else {
1945 this.select(0);
1931 this.select(0);
1946 this.command_mode();
1932 this.command_mode();
1947 };
1933 }
1948 this.set_dirty(false);
1934 this.set_dirty(false);
1949 this.scroll_to_top();
1935 this.scroll_to_top();
1950 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1936 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1951 var msg = "This notebook has been converted from an older " +
1937 var msg = "This notebook has been converted from an older " +
1952 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1938 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1953 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1939 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1954 "newer notebook format will be used and older versions of IPython " +
1940 "newer notebook format will be used and older versions of IPython " +
1955 "may not be able to read it. To keep the older version, close the " +
1941 "may not be able to read it. To keep the older version, close the " +
1956 "notebook without saving it.";
1942 "notebook without saving it.";
1957 IPython.dialog.modal({
1943 IPython.dialog.modal({
1958 title : "Notebook converted",
1944 title : "Notebook converted",
1959 body : msg,
1945 body : msg,
1960 buttons : {
1946 buttons : {
1961 OK : {
1947 OK : {
1962 class : "btn-primary"
1948 class : "btn-primary"
1963 }
1949 }
1964 }
1950 }
1965 });
1951 });
1966 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1952 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1967 var that = this;
1953 var that = this;
1968 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1954 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1969 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1955 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1970 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1956 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1971 this_vs + ". You can still work with this notebook, but some features " +
1957 this_vs + ". You can still work with this notebook, but some features " +
1972 "introduced in later notebook versions may not be available."
1958 "introduced in later notebook versions may not be available.";
1973
1959
1974 IPython.dialog.modal({
1960 IPython.dialog.modal({
1975 title : "Newer Notebook",
1961 title : "Newer Notebook",
1976 body : msg,
1962 body : msg,
1977 buttons : {
1963 buttons : {
1978 OK : {
1964 OK : {
1979 class : "btn-danger"
1965 class : "btn-danger"
1980 }
1966 }
1981 }
1967 }
1982 });
1968 });
1983
1969
1984 }
1970 }
1985
1971
1986 // Create the session after the notebook is completely loaded to prevent
1972 // Create the session after the notebook is completely loaded to prevent
1987 // code execution upon loading, which is a security risk.
1973 // code execution upon loading, which is a security risk.
1988 if (this.session == null) {
1974 if (this.session === null) {
1989 this.start_session();
1975 this.start_session();
1990 }
1976 }
1991 // load our checkpoint list
1977 // load our checkpoint list
1992 this.list_checkpoints();
1978 this.list_checkpoints();
1993
1979
1994 // load toolbar state
1980 // load toolbar state
1995 if (this.metadata.celltoolbar) {
1981 if (this.metadata.celltoolbar) {
1996 IPython.CellToolbar.global_show();
1982 IPython.CellToolbar.global_show();
1997 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1983 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1998 }
1984 }
1999
1985
2000 $([IPython.events]).trigger('notebook_loaded.Notebook');
1986 $([IPython.events]).trigger('notebook_loaded.Notebook');
2001 };
1987 };
2002
1988
2003 /**
1989 /**
2004 * Failure callback for loading a notebook from the server.
1990 * Failure callback for loading a notebook from the server.
2005 *
1991 *
2006 * @method load_notebook_error
1992 * @method load_notebook_error
2007 * @param {jqXHR} xhr jQuery Ajax object
1993 * @param {jqXHR} xhr jQuery Ajax object
2008 * @param {String} status Description of response status
1994 * @param {String} status Description of response status
2009 * @param {String} error HTTP error message
1995 * @param {String} error HTTP error message
2010 */
1996 */
2011 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1997 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2012 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1998 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1999 var msg;
2013 if (xhr.status === 400) {
2000 if (xhr.status === 400) {
2014 var msg = error;
2001 msg = error;
2015 } else if (xhr.status === 500) {
2002 } else if (xhr.status === 500) {
2016 var msg = "An unknown error occurred while loading this notebook. " +
2003 msg = "An unknown error occurred while loading this notebook. " +
2017 "This version can load notebook formats " +
2004 "This version can load notebook formats " +
2018 "v" + this.nbformat + " or earlier.";
2005 "v" + this.nbformat + " or earlier.";
2019 }
2006 }
2020 IPython.dialog.modal({
2007 IPython.dialog.modal({
2021 title: "Error loading notebook",
2008 title: "Error loading notebook",
2022 body : msg,
2009 body : msg,
2023 buttons : {
2010 buttons : {
2024 "OK": {}
2011 "OK": {}
2025 }
2012 }
2026 });
2013 });
2027 }
2014 };
2028
2015
2029 /********************* checkpoint-related *********************/
2016 /********************* checkpoint-related *********************/
2030
2017
2031 /**
2018 /**
2032 * Save the notebook then immediately create a checkpoint.
2019 * Save the notebook then immediately create a checkpoint.
2033 *
2020 *
2034 * @method save_checkpoint
2021 * @method save_checkpoint
2035 */
2022 */
2036 Notebook.prototype.save_checkpoint = function () {
2023 Notebook.prototype.save_checkpoint = function () {
2037 this._checkpoint_after_save = true;
2024 this._checkpoint_after_save = true;
2038 this.save_notebook();
2025 this.save_notebook();
2039 };
2026 };
2040
2027
2041 /**
2028 /**
2042 * Add a checkpoint for this notebook.
2029 * Add a checkpoint for this notebook.
2043 * for use as a callback from checkpoint creation.
2030 * for use as a callback from checkpoint creation.
2044 *
2031 *
2045 * @method add_checkpoint
2032 * @method add_checkpoint
2046 */
2033 */
2047 Notebook.prototype.add_checkpoint = function (checkpoint) {
2034 Notebook.prototype.add_checkpoint = function (checkpoint) {
2048 var found = false;
2035 var found = false;
2049 for (var i = 0; i < this.checkpoints.length; i++) {
2036 for (var i = 0; i < this.checkpoints.length; i++) {
2050 var existing = this.checkpoints[i];
2037 var existing = this.checkpoints[i];
2051 if (existing.id == checkpoint.id) {
2038 if (existing.id == checkpoint.id) {
2052 found = true;
2039 found = true;
2053 this.checkpoints[i] = checkpoint;
2040 this.checkpoints[i] = checkpoint;
2054 break;
2041 break;
2055 }
2042 }
2056 }
2043 }
2057 if (!found) {
2044 if (!found) {
2058 this.checkpoints.push(checkpoint);
2045 this.checkpoints.push(checkpoint);
2059 }
2046 }
2060 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2047 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2061 };
2048 };
2062
2049
2063 /**
2050 /**
2064 * List checkpoints for this notebook.
2051 * List checkpoints for this notebook.
2065 *
2052 *
2066 * @method list_checkpoints
2053 * @method list_checkpoints
2067 */
2054 */
2068 Notebook.prototype.list_checkpoints = function () {
2055 Notebook.prototype.list_checkpoints = function () {
2069 var url = utils.url_join_encode(
2056 var url = utils.url_join_encode(
2070 this._baseProjectUrl,
2057 this.base_url,
2071 'api/notebooks',
2058 'api/notebooks',
2072 this.notebook_path,
2059 this.notebook_path,
2073 this.notebook_name,
2060 this.notebook_name,
2074 'checkpoints'
2061 'checkpoints'
2075 );
2062 );
2076 $.get(url).done(
2063 $.get(url).done(
2077 $.proxy(this.list_checkpoints_success, this)
2064 $.proxy(this.list_checkpoints_success, this)
2078 ).fail(
2065 ).fail(
2079 $.proxy(this.list_checkpoints_error, this)
2066 $.proxy(this.list_checkpoints_error, this)
2080 );
2067 );
2081 };
2068 };
2082
2069
2083 /**
2070 /**
2084 * Success callback for listing checkpoints.
2071 * Success callback for listing checkpoints.
2085 *
2072 *
2086 * @method list_checkpoint_success
2073 * @method list_checkpoint_success
2087 * @param {Object} data JSON representation of a checkpoint
2074 * @param {Object} data JSON representation of a checkpoint
2088 * @param {String} status Description of response status
2075 * @param {String} status Description of response status
2089 * @param {jqXHR} xhr jQuery Ajax object
2076 * @param {jqXHR} xhr jQuery Ajax object
2090 */
2077 */
2091 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2078 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2092 var data = $.parseJSON(data);
2079 data = $.parseJSON(data);
2093 this.checkpoints = data;
2080 this.checkpoints = data;
2094 if (data.length) {
2081 if (data.length) {
2095 this.last_checkpoint = data[data.length - 1];
2082 this.last_checkpoint = data[data.length - 1];
2096 } else {
2083 } else {
2097 this.last_checkpoint = null;
2084 this.last_checkpoint = null;
2098 }
2085 }
2099 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2086 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2100 };
2087 };
2101
2088
2102 /**
2089 /**
2103 * Failure callback for listing a checkpoint.
2090 * Failure callback for listing a checkpoint.
2104 *
2091 *
2105 * @method list_checkpoint_error
2092 * @method list_checkpoint_error
2106 * @param {jqXHR} xhr jQuery Ajax object
2093 * @param {jqXHR} xhr jQuery Ajax object
2107 * @param {String} status Description of response status
2094 * @param {String} status Description of response status
2108 * @param {String} error_msg HTTP error message
2095 * @param {String} error_msg HTTP error message
2109 */
2096 */
2110 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2097 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2111 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2098 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2112 };
2099 };
2113
2100
2114 /**
2101 /**
2115 * Create a checkpoint of this notebook on the server from the most recent save.
2102 * Create a checkpoint of this notebook on the server from the most recent save.
2116 *
2103 *
2117 * @method create_checkpoint
2104 * @method create_checkpoint
2118 */
2105 */
2119 Notebook.prototype.create_checkpoint = function () {
2106 Notebook.prototype.create_checkpoint = function () {
2120 var url = utils.url_join_encode(
2107 var url = utils.url_join_encode(
2121 this._baseProjectUrl,
2108 this.base_url,
2122 'api/notebooks',
2109 'api/notebooks',
2123 this.notebookPath(),
2110 this.notebook_path,
2124 this.notebook_name,
2111 this.notebook_name,
2125 'checkpoints'
2112 'checkpoints'
2126 );
2113 );
2127 $.post(url).done(
2114 $.post(url).done(
2128 $.proxy(this.create_checkpoint_success, this)
2115 $.proxy(this.create_checkpoint_success, this)
2129 ).fail(
2116 ).fail(
2130 $.proxy(this.create_checkpoint_error, this)
2117 $.proxy(this.create_checkpoint_error, this)
2131 );
2118 );
2132 };
2119 };
2133
2120
2134 /**
2121 /**
2135 * Success callback for creating a checkpoint.
2122 * Success callback for creating a checkpoint.
2136 *
2123 *
2137 * @method create_checkpoint_success
2124 * @method create_checkpoint_success
2138 * @param {Object} data JSON representation of a checkpoint
2125 * @param {Object} data JSON representation of a checkpoint
2139 * @param {String} status Description of response status
2126 * @param {String} status Description of response status
2140 * @param {jqXHR} xhr jQuery Ajax object
2127 * @param {jqXHR} xhr jQuery Ajax object
2141 */
2128 */
2142 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2129 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2143 var data = $.parseJSON(data);
2130 data = $.parseJSON(data);
2144 this.add_checkpoint(data);
2131 this.add_checkpoint(data);
2145 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2132 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2146 };
2133 };
2147
2134
2148 /**
2135 /**
2149 * Failure callback for creating a checkpoint.
2136 * Failure callback for creating a checkpoint.
2150 *
2137 *
2151 * @method create_checkpoint_error
2138 * @method create_checkpoint_error
2152 * @param {jqXHR} xhr jQuery Ajax object
2139 * @param {jqXHR} xhr jQuery Ajax object
2153 * @param {String} status Description of response status
2140 * @param {String} status Description of response status
2154 * @param {String} error_msg HTTP error message
2141 * @param {String} error_msg HTTP error message
2155 */
2142 */
2156 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2143 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2157 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2144 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2158 };
2145 };
2159
2146
2160 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2147 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2161 var that = this;
2148 var that = this;
2162 var checkpoint = checkpoint || this.last_checkpoint;
2149 checkpoint = checkpoint || this.last_checkpoint;
2163 if ( ! checkpoint ) {
2150 if ( ! checkpoint ) {
2164 console.log("restore dialog, but no checkpoint to restore to!");
2151 console.log("restore dialog, but no checkpoint to restore to!");
2165 return;
2152 return;
2166 }
2153 }
2167 var body = $('<div/>').append(
2154 var body = $('<div/>').append(
2168 $('<p/>').addClass("p-space").text(
2155 $('<p/>').addClass("p-space").text(
2169 "Are you sure you want to revert the notebook to " +
2156 "Are you sure you want to revert the notebook to " +
2170 "the latest checkpoint?"
2157 "the latest checkpoint?"
2171 ).append(
2158 ).append(
2172 $("<strong/>").text(
2159 $("<strong/>").text(
2173 " This cannot be undone."
2160 " This cannot be undone."
2174 )
2161 )
2175 )
2162 )
2176 ).append(
2163 ).append(
2177 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2164 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2178 ).append(
2165 ).append(
2179 $('<p/>').addClass("p-space").text(
2166 $('<p/>').addClass("p-space").text(
2180 Date(checkpoint.last_modified)
2167 Date(checkpoint.last_modified)
2181 ).css("text-align", "center")
2168 ).css("text-align", "center")
2182 );
2169 );
2183
2170
2184 IPython.dialog.modal({
2171 IPython.dialog.modal({
2185 title : "Revert notebook to checkpoint",
2172 title : "Revert notebook to checkpoint",
2186 body : body,
2173 body : body,
2187 buttons : {
2174 buttons : {
2188 Revert : {
2175 Revert : {
2189 class : "btn-danger",
2176 class : "btn-danger",
2190 click : function () {
2177 click : function () {
2191 that.restore_checkpoint(checkpoint.id);
2178 that.restore_checkpoint(checkpoint.id);
2192 }
2179 }
2193 },
2180 },
2194 Cancel : {}
2181 Cancel : {}
2195 }
2182 }
2196 });
2183 });
2197 }
2184 };
2198
2185
2199 /**
2186 /**
2200 * Restore the notebook to a checkpoint state.
2187 * Restore the notebook to a checkpoint state.
2201 *
2188 *
2202 * @method restore_checkpoint
2189 * @method restore_checkpoint
2203 * @param {String} checkpoint ID
2190 * @param {String} checkpoint ID
2204 */
2191 */
2205 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2192 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2206 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2193 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2207 var url = utils.url_join_encode(
2194 var url = utils.url_join_encode(
2208 this._baseProjectUrl,
2195 this.base_url,
2209 'api/notebooks',
2196 'api/notebooks',
2210 this.notebookPath(),
2197 this.notebook_path,
2211 this.notebook_name,
2198 this.notebook_name,
2212 'checkpoints',
2199 'checkpoints',
2213 checkpoint
2200 checkpoint
2214 );
2201 );
2215 $.post(url).done(
2202 $.post(url).done(
2216 $.proxy(this.restore_checkpoint_success, this)
2203 $.proxy(this.restore_checkpoint_success, this)
2217 ).fail(
2204 ).fail(
2218 $.proxy(this.restore_checkpoint_error, this)
2205 $.proxy(this.restore_checkpoint_error, this)
2219 );
2206 );
2220 };
2207 };
2221
2208
2222 /**
2209 /**
2223 * Success callback for restoring a notebook to a checkpoint.
2210 * Success callback for restoring a notebook to a checkpoint.
2224 *
2211 *
2225 * @method restore_checkpoint_success
2212 * @method restore_checkpoint_success
2226 * @param {Object} data (ignored, should be empty)
2213 * @param {Object} data (ignored, should be empty)
2227 * @param {String} status Description of response status
2214 * @param {String} status Description of response status
2228 * @param {jqXHR} xhr jQuery Ajax object
2215 * @param {jqXHR} xhr jQuery Ajax object
2229 */
2216 */
2230 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2217 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2231 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2218 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2232 this.load_notebook(this.notebook_name, this.notebook_path);
2219 this.load_notebook(this.notebook_name, this.notebook_path);
2233 };
2220 };
2234
2221
2235 /**
2222 /**
2236 * Failure callback for restoring a notebook to a checkpoint.
2223 * Failure callback for restoring a notebook to a checkpoint.
2237 *
2224 *
2238 * @method restore_checkpoint_error
2225 * @method restore_checkpoint_error
2239 * @param {jqXHR} xhr jQuery Ajax object
2226 * @param {jqXHR} xhr jQuery Ajax object
2240 * @param {String} status Description of response status
2227 * @param {String} status Description of response status
2241 * @param {String} error_msg HTTP error message
2228 * @param {String} error_msg HTTP error message
2242 */
2229 */
2243 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2230 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2244 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2231 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2245 };
2232 };
2246
2233
2247 /**
2234 /**
2248 * Delete a notebook checkpoint.
2235 * Delete a notebook checkpoint.
2249 *
2236 *
2250 * @method delete_checkpoint
2237 * @method delete_checkpoint
2251 * @param {String} checkpoint ID
2238 * @param {String} checkpoint ID
2252 */
2239 */
2253 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2240 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2254 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2241 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2255 var url = utils.url_join_encode(
2242 var url = utils.url_join_encode(
2256 this._baseProjectUrl,
2243 this.base_url,
2257 'api/notebooks',
2244 'api/notebooks',
2258 this.notebookPath(),
2245 this.notebook_path,
2259 this.notebook_name,
2246 this.notebook_name,
2260 'checkpoints',
2247 'checkpoints',
2261 checkpoint
2248 checkpoint
2262 );
2249 );
2263 $.ajax(url, {
2250 $.ajax(url, {
2264 type: 'DELETE',
2251 type: 'DELETE',
2265 success: $.proxy(this.delete_checkpoint_success, this),
2252 success: $.proxy(this.delete_checkpoint_success, this),
2266 error: $.proxy(this.delete_notebook_error,this)
2253 error: $.proxy(this.delete_notebook_error,this)
2267 });
2254 });
2268 };
2255 };
2269
2256
2270 /**
2257 /**
2271 * Success callback for deleting a notebook checkpoint
2258 * Success callback for deleting a notebook checkpoint
2272 *
2259 *
2273 * @method delete_checkpoint_success
2260 * @method delete_checkpoint_success
2274 * @param {Object} data (ignored, should be empty)
2261 * @param {Object} data (ignored, should be empty)
2275 * @param {String} status Description of response status
2262 * @param {String} status Description of response status
2276 * @param {jqXHR} xhr jQuery Ajax object
2263 * @param {jqXHR} xhr jQuery Ajax object
2277 */
2264 */
2278 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2265 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2279 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2266 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2280 this.load_notebook(this.notebook_name, this.notebook_path);
2267 this.load_notebook(this.notebook_name, this.notebook_path);
2281 };
2268 };
2282
2269
2283 /**
2270 /**
2284 * Failure callback for deleting a notebook checkpoint.
2271 * Failure callback for deleting a notebook checkpoint.
2285 *
2272 *
2286 * @method delete_checkpoint_error
2273 * @method delete_checkpoint_error
2287 * @param {jqXHR} xhr jQuery Ajax object
2274 * @param {jqXHR} xhr jQuery Ajax object
2288 * @param {String} status Description of response status
2275 * @param {String} status Description of response status
2289 * @param {String} error_msg HTTP error message
2276 * @param {String} error_msg HTTP error message
2290 */
2277 */
2291 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2278 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2292 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2279 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2293 };
2280 };
2294
2281
2295
2282
2296 IPython.Notebook = Notebook;
2283 IPython.Notebook = Notebook;
2297
2284
2298
2285
2299 return IPython;
2286 return IPython;
2300
2287
2301 }(IPython));
2288 }(IPython));
@@ -1,210 +1,222 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2012 The IPython Development Team
2 // Copyright (C) 2012 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notification widget
9 // Notification widget
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16
16
17 var NotificationArea = function (selector) {
17 var NotificationArea = function (selector) {
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 }
21 }
22 this.widget_dict = {};
22 this.widget_dict = {};
23 };
23 };
24
24
25 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
25 NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
26 var uuid = utils.uuid();
26 var uuid = utils.uuid();
27 if( css_class == 'danger') {css_class = 'ui-state-error';}
27 if( css_class == 'danger') {css_class = 'ui-state-error';}
28 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
28 if( css_class == 'warning') {css_class = 'ui-state-highlight';}
29 var tdiv = $('<div>')
29 var tdiv = $('<div>')
30 .attr('id',uuid)
30 .attr('id',uuid)
31 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
31 .addClass('notification_widget ui-widget ui-widget-content ui-corner-all')
32 .addClass('border-box-sizing')
32 .addClass('border-box-sizing')
33 .addClass(css_class)
33 .addClass(css_class)
34 .hide()
34 .hide()
35 .text(msg);
35 .text(msg);
36
36
37 $(this.selector).append(tdiv);
37 $(this.selector).append(tdiv);
38 var tmout = Math.max(1500,(timeout||1500));
38 var tmout = Math.max(1500,(timeout||1500));
39 tdiv.fadeIn(100);
39 tdiv.fadeIn(100);
40
40
41 setTimeout(function () {
41 setTimeout(function () {
42 tdiv.fadeOut(100, function () {tdiv.remove();});
42 tdiv.fadeOut(100, function () {tdiv.remove();});
43 }, tmout);
43 }, tmout);
44 };
44 };
45
45
46 NotificationArea.prototype.widget = function(name) {
46 NotificationArea.prototype.widget = function(name) {
47 if(this.widget_dict[name] == undefined) {
47 if(this.widget_dict[name] == undefined) {
48 return this.new_notification_widget(name);
48 return this.new_notification_widget(name);
49 }
49 }
50 return this.get_widget(name);
50 return this.get_widget(name);
51 };
51 };
52
52
53 NotificationArea.prototype.get_widget = function(name) {
53 NotificationArea.prototype.get_widget = function(name) {
54 if(this.widget_dict[name] == undefined) {
54 if(this.widget_dict[name] == undefined) {
55 throw('no widgets with this name');
55 throw('no widgets with this name');
56 }
56 }
57 return this.widget_dict[name];
57 return this.widget_dict[name];
58 };
58 };
59
59
60 NotificationArea.prototype.new_notification_widget = function(name) {
60 NotificationArea.prototype.new_notification_widget = function(name) {
61 if(this.widget_dict[name] != undefined) {
61 if(this.widget_dict[name] != undefined) {
62 throw('widget with that name already exists ! ');
62 throw('widget with that name already exists ! ');
63 }
63 }
64 var div = $('<div/>').attr('id','notification_'+name);
64 var div = $('<div/>').attr('id','notification_'+name);
65 $(this.selector).append(div);
65 $(this.selector).append(div);
66 this.widget_dict[name] = new IPython.NotificationWidget('#notification_'+name);
66 this.widget_dict[name] = new IPython.NotificationWidget('#notification_'+name);
67 return this.widget_dict[name];
67 return this.widget_dict[name];
68 };
68 };
69
69
70 NotificationArea.prototype.init_notification_widgets = function() {
70 NotificationArea.prototype.init_notification_widgets = function() {
71 var knw = this.new_notification_widget('kernel');
71 var knw = this.new_notification_widget('kernel');
72 var $kernel_indic = $("#kernel_indicator");
72 var $kernel_ind_icon = $("#kernel_indicator_icon");
73 var $modal_ind_icon = $("#modal_indicator_icon");
74
75 // Command/Edit mode
76 $([IPython.events]).on('edit_mode.Notebook',function () {
77 IPython.save_widget.update_document_title();
78 $modal_ind_icon.attr('class','icon-pencil').attr('title','Edit Mode');
79 });
80
81 $([IPython.events]).on('command_mode.Notebook',function () {
82 IPython.save_widget.update_document_title();
83 $modal_ind_icon.attr('class','').attr('title','Command Mode');
84 });
73
85
74 // Kernel events
86 // Kernel events
75 $([IPython.events]).on('status_idle.Kernel',function () {
87 $([IPython.events]).on('status_idle.Kernel',function () {
76 IPython.save_widget.update_document_title();
88 IPython.save_widget.update_document_title();
77 $kernel_indic.attr('class','icon-circle-blank').attr('title','Kernel Idle');
89 $kernel_ind_icon.attr('class','icon-circle-blank').attr('title','Kernel Idle');
78 });
90 });
79
91
80 $([IPython.events]).on('status_busy.Kernel',function () {
92 $([IPython.events]).on('status_busy.Kernel',function () {
81 window.document.title='(Busy) '+window.document.title;
93 window.document.title='(Busy) '+window.document.title;
82 $kernel_indic.attr('class','icon-circle').attr('title','Kernel Busy');
94 $kernel_ind_icon.attr('class','icon-circle').attr('title','Kernel Busy');
83 });
95 });
84
96
85 $([IPython.events]).on('status_restarting.Kernel',function () {
97 $([IPython.events]).on('status_restarting.Kernel',function () {
86 IPython.save_widget.update_document_title();
98 IPython.save_widget.update_document_title();
87 knw.set_message("Restarting kernel", 2000);
99 knw.set_message("Restarting kernel", 2000);
88 });
100 });
89
101
90 $([IPython.events]).on('status_interrupting.Kernel',function () {
102 $([IPython.events]).on('status_interrupting.Kernel',function () {
91 knw.set_message("Interrupting kernel", 2000);
103 knw.set_message("Interrupting kernel", 2000);
92 });
104 });
93
105
94 $([IPython.events]).on('status_dead.Kernel',function () {
106 $([IPython.events]).on('status_dead.Kernel',function () {
95 var msg = 'The kernel has died, and the automatic restart has failed.' +
107 var msg = 'The kernel has died, and the automatic restart has failed.' +
96 ' It is possible the kernel cannot be restarted.' +
108 ' It is possible the kernel cannot be restarted.' +
97 ' If you are not able to restart the kernel, you will still be able to save' +
109 ' If you are not able to restart the kernel, you will still be able to save' +
98 ' the notebook, but running code will no longer work until the notebook' +
110 ' the notebook, but running code will no longer work until the notebook' +
99 ' is reopened.';
111 ' is reopened.';
100
112
101 IPython.dialog.modal({
113 IPython.dialog.modal({
102 title: "Dead kernel",
114 title: "Dead kernel",
103 body : msg,
115 body : msg,
104 buttons : {
116 buttons : {
105 "Manual Restart": {
117 "Manual Restart": {
106 class: "btn-danger",
118 class: "btn-danger",
107 click: function () {
119 click: function () {
108 $([IPython.events]).trigger('status_restarting.Kernel');
120 $([IPython.events]).trigger('status_restarting.Kernel');
109 IPython.notebook.start_kernel();
121 IPython.notebook.start_kernel();
110 }
122 }
111 },
123 },
112 "Don't restart": {}
124 "Don't restart": {}
113 }
125 }
114 });
126 });
115 });
127 });
116
128
117 $([IPython.events]).on('websocket_closed.Kernel', function (event, data) {
129 $([IPython.events]).on('websocket_closed.Kernel', function (event, data) {
118 var kernel = data.kernel;
130 var kernel = data.kernel;
119 var ws_url = data.ws_url;
131 var ws_url = data.ws_url;
120 var early = data.early;
132 var early = data.early;
121 var msg;
133 var msg;
122 if (!early) {
134 if (!early) {
123 knw.set_message('Reconnecting WebSockets', 1000);
135 knw.set_message('Reconnecting WebSockets', 1000);
124 setTimeout(function () {
136 setTimeout(function () {
125 kernel.start_channels();
137 kernel.start_channels();
126 }, 5000);
138 }, 5000);
127 return;
139 return;
128 }
140 }
129 console.log('WebSocket connection failed: ', ws_url)
141 console.log('WebSocket connection failed: ', ws_url)
130 msg = "A WebSocket connection could not be established." +
142 msg = "A WebSocket connection could not be established." +
131 " You will NOT be able to run code. Check your" +
143 " You will NOT be able to run code. Check your" +
132 " network connection or notebook server configuration.";
144 " network connection or notebook server configuration.";
133 IPython.dialog.modal({
145 IPython.dialog.modal({
134 title: "WebSocket connection failed",
146 title: "WebSocket connection failed",
135 body: msg,
147 body: msg,
136 buttons : {
148 buttons : {
137 "OK": {},
149 "OK": {},
138 "Reconnect": {
150 "Reconnect": {
139 click: function () {
151 click: function () {
140 knw.set_message('Reconnecting WebSockets', 1000);
152 knw.set_message('Reconnecting WebSockets', 1000);
141 setTimeout(function () {
153 setTimeout(function () {
142 kernel.start_channels();
154 kernel.start_channels();
143 }, 5000);
155 }, 5000);
144 }
156 }
145 }
157 }
146 }
158 }
147 });
159 });
148 });
160 });
149
161
150
162
151 var nnw = this.new_notification_widget('notebook');
163 var nnw = this.new_notification_widget('notebook');
152
164
153 // Notebook events
165 // Notebook events
154 $([IPython.events]).on('notebook_loading.Notebook', function () {
166 $([IPython.events]).on('notebook_loading.Notebook', function () {
155 nnw.set_message("Loading notebook",500);
167 nnw.set_message("Loading notebook",500);
156 });
168 });
157 $([IPython.events]).on('notebook_loaded.Notebook', function () {
169 $([IPython.events]).on('notebook_loaded.Notebook', function () {
158 nnw.set_message("Notebook loaded",500);
170 nnw.set_message("Notebook loaded",500);
159 });
171 });
160 $([IPython.events]).on('notebook_saving.Notebook', function () {
172 $([IPython.events]).on('notebook_saving.Notebook', function () {
161 nnw.set_message("Saving notebook",500);
173 nnw.set_message("Saving notebook",500);
162 });
174 });
163 $([IPython.events]).on('notebook_saved.Notebook', function () {
175 $([IPython.events]).on('notebook_saved.Notebook', function () {
164 nnw.set_message("Notebook saved",2000);
176 nnw.set_message("Notebook saved",2000);
165 });
177 });
166 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
178 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
167 nnw.set_message("Notebook save failed");
179 nnw.set_message("Notebook save failed");
168 });
180 });
169
181
170 // Checkpoint events
182 // Checkpoint events
171 $([IPython.events]).on('checkpoint_created.Notebook', function (evt, data) {
183 $([IPython.events]).on('checkpoint_created.Notebook', function (evt, data) {
172 var msg = "Checkpoint created";
184 var msg = "Checkpoint created";
173 if (data.last_modified) {
185 if (data.last_modified) {
174 var d = new Date(data.last_modified);
186 var d = new Date(data.last_modified);
175 msg = msg + ": " + d.format("HH:MM:ss");
187 msg = msg + ": " + d.format("HH:MM:ss");
176 }
188 }
177 nnw.set_message(msg, 2000);
189 nnw.set_message(msg, 2000);
178 });
190 });
179 $([IPython.events]).on('checkpoint_failed.Notebook', function () {
191 $([IPython.events]).on('checkpoint_failed.Notebook', function () {
180 nnw.set_message("Checkpoint failed");
192 nnw.set_message("Checkpoint failed");
181 });
193 });
182 $([IPython.events]).on('checkpoint_deleted.Notebook', function () {
194 $([IPython.events]).on('checkpoint_deleted.Notebook', function () {
183 nnw.set_message("Checkpoint deleted", 500);
195 nnw.set_message("Checkpoint deleted", 500);
184 });
196 });
185 $([IPython.events]).on('checkpoint_delete_failed.Notebook', function () {
197 $([IPython.events]).on('checkpoint_delete_failed.Notebook', function () {
186 nnw.set_message("Checkpoint delete failed");
198 nnw.set_message("Checkpoint delete failed");
187 });
199 });
188 $([IPython.events]).on('checkpoint_restoring.Notebook', function () {
200 $([IPython.events]).on('checkpoint_restoring.Notebook', function () {
189 nnw.set_message("Restoring to checkpoint...", 500);
201 nnw.set_message("Restoring to checkpoint...", 500);
190 });
202 });
191 $([IPython.events]).on('checkpoint_restore_failed.Notebook', function () {
203 $([IPython.events]).on('checkpoint_restore_failed.Notebook', function () {
192 nnw.set_message("Checkpoint restore failed");
204 nnw.set_message("Checkpoint restore failed");
193 });
205 });
194
206
195 // Autosave events
207 // Autosave events
196 $([IPython.events]).on('autosave_disabled.Notebook', function () {
208 $([IPython.events]).on('autosave_disabled.Notebook', function () {
197 nnw.set_message("Autosave disabled", 2000);
209 nnw.set_message("Autosave disabled", 2000);
198 });
210 });
199 $([IPython.events]).on('autosave_enabled.Notebook', function (evt, interval) {
211 $([IPython.events]).on('autosave_enabled.Notebook', function (evt, interval) {
200 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
212 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
201 });
213 });
202
214
203 };
215 };
204
216
205 IPython.NotificationArea = NotificationArea;
217 IPython.NotificationArea = NotificationArea;
206
218
207 return IPython;
219 return IPython;
208
220
209 }(IPython));
221 }(IPython));
210
222
@@ -1,851 +1,867 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008 The IPython Development Team
2 // Copyright (C) 2008 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // OutputArea
9 // OutputArea
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule OutputArea
15 * @submodule OutputArea
16 */
16 */
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18 "use strict";
19
19
20 var utils = IPython.utils;
20 var utils = IPython.utils;
21
21
22 /**
22 /**
23 * @class OutputArea
23 * @class OutputArea
24 *
24 *
25 * @constructor
25 * @constructor
26 */
26 */
27
27
28 var OutputArea = function (selector, prompt_area) {
28 var OutputArea = function (selector, prompt_area) {
29 this.selector = selector;
29 this.selector = selector;
30 this.wrapper = $(selector);
30 this.wrapper = $(selector);
31 this.outputs = [];
31 this.outputs = [];
32 this.collapsed = false;
32 this.collapsed = false;
33 this.scrolled = false;
33 this.scrolled = false;
34 this.trusted = true;
34 this.trusted = true;
35 this.clear_queued = null;
35 this.clear_queued = null;
36 if (prompt_area === undefined) {
36 if (prompt_area === undefined) {
37 this.prompt_area = true;
37 this.prompt_area = true;
38 } else {
38 } else {
39 this.prompt_area = prompt_area;
39 this.prompt_area = prompt_area;
40 }
40 }
41 this.create_elements();
41 this.create_elements();
42 this.style();
42 this.style();
43 this.bind_events();
43 this.bind_events();
44 };
44 };
45
45
46
46
47 /**
47 /**
48 * Class prototypes
48 * Class prototypes
49 **/
49 **/
50
50
51 OutputArea.prototype.create_elements = function () {
51 OutputArea.prototype.create_elements = function () {
52 this.element = $("<div/>");
52 this.element = $("<div/>");
53 this.collapse_button = $("<div/>");
53 this.collapse_button = $("<div/>");
54 this.prompt_overlay = $("<div/>");
54 this.prompt_overlay = $("<div/>");
55 this.wrapper.append(this.prompt_overlay);
55 this.wrapper.append(this.prompt_overlay);
56 this.wrapper.append(this.element);
56 this.wrapper.append(this.element);
57 this.wrapper.append(this.collapse_button);
57 this.wrapper.append(this.collapse_button);
58 };
58 };
59
59
60
60
61 OutputArea.prototype.style = function () {
61 OutputArea.prototype.style = function () {
62 this.collapse_button.hide();
62 this.collapse_button.hide();
63 this.prompt_overlay.hide();
63 this.prompt_overlay.hide();
64
64
65 this.wrapper.addClass('output_wrapper');
65 this.wrapper.addClass('output_wrapper');
66 this.element.addClass('output');
66 this.element.addClass('output');
67
67
68 this.collapse_button.addClass("btn output_collapsed");
68 this.collapse_button.addClass("btn output_collapsed");
69 this.collapse_button.attr('title', 'click to expand output');
69 this.collapse_button.attr('title', 'click to expand output');
70 this.collapse_button.text('. . .');
70 this.collapse_button.text('. . .');
71
71
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
74
74
75 this.collapse();
75 this.collapse();
76 };
76 };
77
77
78 /**
78 /**
79 * Should the OutputArea scroll?
79 * Should the OutputArea scroll?
80 * Returns whether the height (in lines) exceeds a threshold.
80 * Returns whether the height (in lines) exceeds a threshold.
81 *
81 *
82 * @private
82 * @private
83 * @method _should_scroll
83 * @method _should_scroll
84 * @param [lines=100]{Integer}
84 * @param [lines=100]{Integer}
85 * @return {Bool}
85 * @return {Bool}
86 *
86 *
87 */
87 */
88 OutputArea.prototype._should_scroll = function (lines) {
88 OutputArea.prototype._should_scroll = function (lines) {
89 if (lines <=0 ){ return }
89 if (lines <=0 ){ return }
90 if (!lines) {
90 if (!lines) {
91 lines = 100;
91 lines = 100;
92 }
92 }
93 // line-height from http://stackoverflow.com/questions/1185151
93 // line-height from http://stackoverflow.com/questions/1185151
94 var fontSize = this.element.css('font-size');
94 var fontSize = this.element.css('font-size');
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
96
96
97 return (this.element.height() > lines * lineHeight);
97 return (this.element.height() > lines * lineHeight);
98 };
98 };
99
99
100
100
101 OutputArea.prototype.bind_events = function () {
101 OutputArea.prototype.bind_events = function () {
102 var that = this;
102 var that = this;
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
105
105
106 this.element.resize(function () {
106 this.element.resize(function () {
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
108 if ( IPython.utils.browser[0] === "Firefox" ) {
108 if ( IPython.utils.browser[0] === "Firefox" ) {
109 return;
109 return;
110 }
110 }
111 // maybe scroll output,
111 // maybe scroll output,
112 // if it's grown large enough and hasn't already been scrolled.
112 // if it's grown large enough and hasn't already been scrolled.
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
114 that.scroll_area();
114 that.scroll_area();
115 }
115 }
116 });
116 });
117 this.collapse_button.click(function () {
117 this.collapse_button.click(function () {
118 that.expand();
118 that.expand();
119 });
119 });
120 };
120 };
121
121
122
122
123 OutputArea.prototype.collapse = function () {
123 OutputArea.prototype.collapse = function () {
124 if (!this.collapsed) {
124 if (!this.collapsed) {
125 this.element.hide();
125 this.element.hide();
126 this.prompt_overlay.hide();
126 this.prompt_overlay.hide();
127 if (this.element.html()){
127 if (this.element.html()){
128 this.collapse_button.show();
128 this.collapse_button.show();
129 }
129 }
130 this.collapsed = true;
130 this.collapsed = true;
131 }
131 }
132 };
132 };
133
133
134
134
135 OutputArea.prototype.expand = function () {
135 OutputArea.prototype.expand = function () {
136 if (this.collapsed) {
136 if (this.collapsed) {
137 this.collapse_button.hide();
137 this.collapse_button.hide();
138 this.element.show();
138 this.element.show();
139 this.prompt_overlay.show();
139 this.prompt_overlay.show();
140 this.collapsed = false;
140 this.collapsed = false;
141 }
141 }
142 };
142 };
143
143
144
144
145 OutputArea.prototype.toggle_output = function () {
145 OutputArea.prototype.toggle_output = function () {
146 if (this.collapsed) {
146 if (this.collapsed) {
147 this.expand();
147 this.expand();
148 } else {
148 } else {
149 this.collapse();
149 this.collapse();
150 }
150 }
151 };
151 };
152
152
153
153
154 OutputArea.prototype.scroll_area = function () {
154 OutputArea.prototype.scroll_area = function () {
155 this.element.addClass('output_scroll');
155 this.element.addClass('output_scroll');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
157 this.scrolled = true;
157 this.scrolled = true;
158 };
158 };
159
159
160
160
161 OutputArea.prototype.unscroll_area = function () {
161 OutputArea.prototype.unscroll_area = function () {
162 this.element.removeClass('output_scroll');
162 this.element.removeClass('output_scroll');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
164 this.scrolled = false;
164 this.scrolled = false;
165 };
165 };
166
166
167 /**
167 /**
168 *
168 *
169 * Scroll OutputArea if height supperior than a threshold (in lines).
169 * Scroll OutputArea if height supperior than a threshold (in lines).
170 *
170 *
171 * Threshold is a maximum number of lines. If unspecified, defaults to
171 * Threshold is a maximum number of lines. If unspecified, defaults to
172 * OutputArea.minimum_scroll_threshold.
172 * OutputArea.minimum_scroll_threshold.
173 *
173 *
174 * Negative threshold will prevent the OutputArea from ever scrolling.
174 * Negative threshold will prevent the OutputArea from ever scrolling.
175 *
175 *
176 * @method scroll_if_long
176 * @method scroll_if_long
177 *
177 *
178 * @param [lines=20]{Number} Default to 20 if not set,
178 * @param [lines=20]{Number} Default to 20 if not set,
179 * behavior undefined for value of `0`.
179 * behavior undefined for value of `0`.
180 *
180 *
181 **/
181 **/
182 OutputArea.prototype.scroll_if_long = function (lines) {
182 OutputArea.prototype.scroll_if_long = function (lines) {
183 var n = lines | OutputArea.minimum_scroll_threshold;
183 var n = lines | OutputArea.minimum_scroll_threshold;
184 if(n <= 0){
184 if(n <= 0){
185 return
185 return
186 }
186 }
187
187
188 if (this._should_scroll(n)) {
188 if (this._should_scroll(n)) {
189 // only allow scrolling long-enough output
189 // only allow scrolling long-enough output
190 this.scroll_area();
190 this.scroll_area();
191 }
191 }
192 };
192 };
193
193
194
194
195 OutputArea.prototype.toggle_scroll = function () {
195 OutputArea.prototype.toggle_scroll = function () {
196 if (this.scrolled) {
196 if (this.scrolled) {
197 this.unscroll_area();
197 this.unscroll_area();
198 } else {
198 } else {
199 // only allow scrolling long-enough output
199 // only allow scrolling long-enough output
200 this.scroll_if_long();
200 this.scroll_if_long();
201 }
201 }
202 };
202 };
203
203
204
204
205 // typeset with MathJax if MathJax is available
205 // typeset with MathJax if MathJax is available
206 OutputArea.prototype.typeset = function () {
206 OutputArea.prototype.typeset = function () {
207 if (window.MathJax){
207 if (window.MathJax){
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
209 }
209 }
210 };
210 };
211
211
212
212
213 OutputArea.prototype.handle_output = function (msg) {
213 OutputArea.prototype.handle_output = function (msg) {
214 var json = {};
214 var json = {};
215 var msg_type = json.output_type = msg.header.msg_type;
215 var msg_type = json.output_type = msg.header.msg_type;
216 var content = msg.content;
216 var content = msg.content;
217 if (msg_type === "stream") {
217 if (msg_type === "stream") {
218 json.text = content.data;
218 json.text = content.data;
219 json.stream = content.name;
219 json.stream = content.name;
220 } else if (msg_type === "display_data") {
220 } else if (msg_type === "display_data") {
221 json = content.data;
221 json = content.data;
222 json.output_type = msg_type;
222 json.output_type = msg_type;
223 json.metadata = content.metadata;
223 json.metadata = content.metadata;
224 } else if (msg_type === "pyout") {
224 } else if (msg_type === "pyout") {
225 json = content.data;
225 json = content.data;
226 json.output_type = msg_type;
226 json.output_type = msg_type;
227 json.metadata = content.metadata;
227 json.metadata = content.metadata;
228 json.prompt_number = content.execution_count;
228 json.prompt_number = content.execution_count;
229 } else if (msg_type === "pyerr") {
229 } else if (msg_type === "pyerr") {
230 json.ename = content.ename;
230 json.ename = content.ename;
231 json.evalue = content.evalue;
231 json.evalue = content.evalue;
232 json.traceback = content.traceback;
232 json.traceback = content.traceback;
233 }
233 }
234 this.append_output(json);
234 this.append_output(json);
235 };
235 };
236
236
237
237
238 OutputArea.prototype.rename_keys = function (data, key_map) {
238 OutputArea.prototype.rename_keys = function (data, key_map) {
239 var remapped = {};
239 var remapped = {};
240 for (var key in data) {
240 for (var key in data) {
241 var new_key = key_map[key] || key;
241 var new_key = key_map[key] || key;
242 remapped[new_key] = data[key];
242 remapped[new_key] = data[key];
243 }
243 }
244 return remapped;
244 return remapped;
245 };
245 };
246
246
247
247
248 OutputArea.output_types = [
248 OutputArea.output_types = [
249 'application/javascript',
249 'application/javascript',
250 'text/html',
250 'text/html',
251 'text/latex',
251 'text/latex',
252 'image/svg+xml',
252 'image/svg+xml',
253 'image/png',
253 'image/png',
254 'image/jpeg',
254 'image/jpeg',
255 'application/pdf',
255 'text/plain'
256 'text/plain'
256 ];
257 ];
257
258
258 OutputArea.prototype.validate_output = function (json) {
259 OutputArea.prototype.validate_output = function (json) {
259 // scrub invalid outputs
260 // scrub invalid outputs
260 // TODO: right now everything is a string, but JSON really shouldn't be.
261 // TODO: right now everything is a string, but JSON really shouldn't be.
261 // nbformat 4 will fix that.
262 // nbformat 4 will fix that.
262 $.map(OutputArea.output_types, function(key){
263 $.map(OutputArea.output_types, function(key){
263 if (json[key] !== undefined && typeof json[key] !== 'string') {
264 if (json[key] !== undefined && typeof json[key] !== 'string') {
264 console.log("Invalid type for " + key, json[key]);
265 console.log("Invalid type for " + key, json[key]);
265 delete json[key];
266 delete json[key];
266 }
267 }
267 });
268 });
268 return json;
269 return json;
269 };
270 };
270
271
271 OutputArea.prototype.append_output = function (json) {
272 OutputArea.prototype.append_output = function (json) {
272 this.expand();
273 this.expand();
273 // Clear the output if clear is queued.
274 // Clear the output if clear is queued.
274 var needs_height_reset = false;
275 var needs_height_reset = false;
275 if (this.clear_queued) {
276 if (this.clear_queued) {
276 this.clear_output(false);
277 this.clear_output(false);
277 needs_height_reset = true;
278 needs_height_reset = true;
278 }
279 }
279
280
280 // validate output data types
281 // validate output data types
281 json = this.validate_output(json);
282 json = this.validate_output(json);
282
283
283 if (json.output_type === 'pyout') {
284 if (json.output_type === 'pyout') {
284 this.append_pyout(json);
285 this.append_pyout(json);
285 } else if (json.output_type === 'pyerr') {
286 } else if (json.output_type === 'pyerr') {
286 this.append_pyerr(json);
287 this.append_pyerr(json);
287 } else if (json.output_type === 'display_data') {
288 } else if (json.output_type === 'display_data') {
288 this.append_display_data(json);
289 this.append_display_data(json);
289 } else if (json.output_type === 'stream') {
290 } else if (json.output_type === 'stream') {
290 this.append_stream(json);
291 this.append_stream(json);
291 }
292 }
292
293
293 this.outputs.push(json);
294 this.outputs.push(json);
294
295
295 // Only reset the height to automatic if the height is currently
296 // Only reset the height to automatic if the height is currently
296 // fixed (done by wait=True flag on clear_output).
297 // fixed (done by wait=True flag on clear_output).
297 if (needs_height_reset) {
298 if (needs_height_reset) {
298 this.element.height('');
299 this.element.height('');
299 }
300 }
300
301
301 var that = this;
302 var that = this;
302 setTimeout(function(){that.element.trigger('resize');}, 100);
303 setTimeout(function(){that.element.trigger('resize');}, 100);
303 };
304 };
304
305
305
306
306 OutputArea.prototype.create_output_area = function () {
307 OutputArea.prototype.create_output_area = function () {
307 var oa = $("<div/>").addClass("output_area");
308 var oa = $("<div/>").addClass("output_area");
308 if (this.prompt_area) {
309 if (this.prompt_area) {
309 oa.append($('<div/>').addClass('prompt'));
310 oa.append($('<div/>').addClass('prompt'));
310 }
311 }
311 return oa;
312 return oa;
312 };
313 };
313
314
314
315
315 function _get_metadata_key(metadata, key, mime) {
316 function _get_metadata_key(metadata, key, mime) {
316 var mime_md = metadata[mime];
317 var mime_md = metadata[mime];
317 // mime-specific higher priority
318 // mime-specific higher priority
318 if (mime_md && mime_md[key] !== undefined) {
319 if (mime_md && mime_md[key] !== undefined) {
319 return mime_md[key];
320 return mime_md[key];
320 }
321 }
321 // fallback on global
322 // fallback on global
322 return metadata[key];
323 return metadata[key];
323 }
324 }
324
325
325 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
326 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
326 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
327 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
327 if (_get_metadata_key(md, 'isolated', mime)) {
328 if (_get_metadata_key(md, 'isolated', mime)) {
328 // Create an iframe to isolate the subarea from the rest of the
329 // Create an iframe to isolate the subarea from the rest of the
329 // document
330 // document
330 var iframe = $('<iframe/>').addClass('box-flex1');
331 var iframe = $('<iframe/>').addClass('box-flex1');
331 iframe.css({'height':1, 'width':'100%', 'display':'block'});
332 iframe.css({'height':1, 'width':'100%', 'display':'block'});
332 iframe.attr('frameborder', 0);
333 iframe.attr('frameborder', 0);
333 iframe.attr('scrolling', 'auto');
334 iframe.attr('scrolling', 'auto');
334
335
335 // Once the iframe is loaded, the subarea is dynamically inserted
336 // Once the iframe is loaded, the subarea is dynamically inserted
336 iframe.on('load', function() {
337 iframe.on('load', function() {
337 // Workaround needed by Firefox, to properly render svg inside
338 // Workaround needed by Firefox, to properly render svg inside
338 // iframes, see http://stackoverflow.com/questions/10177190/
339 // iframes, see http://stackoverflow.com/questions/10177190/
339 // svg-dynamically-added-to-iframe-does-not-render-correctly
340 // svg-dynamically-added-to-iframe-does-not-render-correctly
340 this.contentDocument.open();
341 this.contentDocument.open();
341
342
342 // Insert the subarea into the iframe
343 // Insert the subarea into the iframe
343 // We must directly write the html. When using Jquery's append
344 // We must directly write the html. When using Jquery's append
344 // method, javascript is evaluated in the parent document and
345 // method, javascript is evaluated in the parent document and
345 // not in the iframe document.
346 // not in the iframe document.
346 this.contentDocument.write(subarea.html());
347 this.contentDocument.write(subarea.html());
347
348
348 this.contentDocument.close();
349 this.contentDocument.close();
349
350
350 var body = this.contentDocument.body;
351 var body = this.contentDocument.body;
351 // Adjust the iframe height automatically
352 // Adjust the iframe height automatically
352 iframe.height(body.scrollHeight + 'px');
353 iframe.height(body.scrollHeight + 'px');
353 });
354 });
354
355
355 // Elements should be appended to the inner subarea and not to the
356 // Elements should be appended to the inner subarea and not to the
356 // iframe
357 // iframe
357 iframe.append = function(that) {
358 iframe.append = function(that) {
358 subarea.append(that);
359 subarea.append(that);
359 };
360 };
360
361
361 return iframe;
362 return iframe;
362 } else {
363 } else {
363 return subarea;
364 return subarea;
364 }
365 }
365 }
366 }
366
367
367
368
368 OutputArea.prototype._append_javascript_error = function (err, element) {
369 OutputArea.prototype._append_javascript_error = function (err, element) {
369 // display a message when a javascript error occurs in display output
370 // display a message when a javascript error occurs in display output
370 var msg = "Javascript error adding output!"
371 var msg = "Javascript error adding output!"
371 if ( element === undefined ) return;
372 if ( element === undefined ) return;
372 element.append(
373 element.append(
373 $('<div/>').html(msg + "<br/>" +
374 $('<div/>').html(msg + "<br/>" +
374 err.toString() +
375 err.toString() +
375 '<br/>See your browser Javascript console for more details.'
376 '<br/>See your browser Javascript console for more details.'
376 ).addClass('js-error')
377 ).addClass('js-error')
377 );
378 );
378 };
379 };
379
380
380 OutputArea.prototype._safe_append = function (toinsert) {
381 OutputArea.prototype._safe_append = function (toinsert) {
381 // safely append an item to the document
382 // safely append an item to the document
382 // this is an object created by user code,
383 // this is an object created by user code,
383 // and may have errors, which should not be raised
384 // and may have errors, which should not be raised
384 // under any circumstances.
385 // under any circumstances.
385 try {
386 try {
386 this.element.append(toinsert);
387 this.element.append(toinsert);
387 } catch(err) {
388 } catch(err) {
388 console.log(err);
389 console.log(err);
389 // Create an actual output_area and output_subarea, which creates
390 // Create an actual output_area and output_subarea, which creates
390 // the prompt area and the proper indentation.
391 // the prompt area and the proper indentation.
391 var toinsert = this.create_output_area();
392 var toinsert = this.create_output_area();
392 var subarea = $('<div/>').addClass('output_subarea');
393 var subarea = $('<div/>').addClass('output_subarea');
393 toinsert.append(subarea);
394 toinsert.append(subarea);
394 this._append_javascript_error(err, subarea);
395 this._append_javascript_error(err, subarea);
395 this.element.append(toinsert);
396 this.element.append(toinsert);
396 }
397 }
397 };
398 };
398
399
399
400
400 OutputArea.prototype.append_pyout = function (json) {
401 OutputArea.prototype.append_pyout = function (json) {
401 var n = json.prompt_number || ' ';
402 var n = json.prompt_number || ' ';
402 var toinsert = this.create_output_area();
403 var toinsert = this.create_output_area();
403 if (this.prompt_area) {
404 if (this.prompt_area) {
404 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
405 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
405 }
406 }
406 this.append_mime_type(json, toinsert);
407 this.append_mime_type(json, toinsert);
407 this._safe_append(toinsert);
408 this._safe_append(toinsert);
408 // If we just output latex, typeset it.
409 // If we just output latex, typeset it.
409 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
410 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
410 this.typeset();
411 this.typeset();
411 }
412 }
412 };
413 };
413
414
414
415
415 OutputArea.prototype.append_pyerr = function (json) {
416 OutputArea.prototype.append_pyerr = function (json) {
416 var tb = json.traceback;
417 var tb = json.traceback;
417 if (tb !== undefined && tb.length > 0) {
418 if (tb !== undefined && tb.length > 0) {
418 var s = '';
419 var s = '';
419 var len = tb.length;
420 var len = tb.length;
420 for (var i=0; i<len; i++) {
421 for (var i=0; i<len; i++) {
421 s = s + tb[i] + '\n';
422 s = s + tb[i] + '\n';
422 }
423 }
423 s = s + '\n';
424 s = s + '\n';
424 var toinsert = this.create_output_area();
425 var toinsert = this.create_output_area();
425 this.append_text(s, {}, toinsert);
426 this.append_text(s, {}, toinsert);
426 this._safe_append(toinsert);
427 this._safe_append(toinsert);
427 }
428 }
428 };
429 };
429
430
430
431
431 OutputArea.prototype.append_stream = function (json) {
432 OutputArea.prototype.append_stream = function (json) {
432 // temporary fix: if stream undefined (json file written prior to this patch),
433 // temporary fix: if stream undefined (json file written prior to this patch),
433 // default to most likely stdout:
434 // default to most likely stdout:
434 if (json.stream == undefined){
435 if (json.stream == undefined){
435 json.stream = 'stdout';
436 json.stream = 'stdout';
436 }
437 }
437 var text = json.text;
438 var text = json.text;
438 var subclass = "output_"+json.stream;
439 var subclass = "output_"+json.stream;
439 if (this.outputs.length > 0){
440 if (this.outputs.length > 0){
440 // have at least one output to consider
441 // have at least one output to consider
441 var last = this.outputs[this.outputs.length-1];
442 var last = this.outputs[this.outputs.length-1];
442 if (last.output_type == 'stream' && json.stream == last.stream){
443 if (last.output_type == 'stream' && json.stream == last.stream){
443 // latest output was in the same stream,
444 // latest output was in the same stream,
444 // so append directly into its pre tag
445 // so append directly into its pre tag
445 // escape ANSI & HTML specials:
446 // escape ANSI & HTML specials:
446 var pre = this.element.find('div.'+subclass).last().find('pre');
447 var pre = this.element.find('div.'+subclass).last().find('pre');
447 var html = utils.fixCarriageReturn(
448 var html = utils.fixCarriageReturn(
448 pre.html() + utils.fixConsole(text));
449 pre.html() + utils.fixConsole(text));
449 pre.html(html);
450 pre.html(html);
450 return;
451 return;
451 }
452 }
452 }
453 }
453
454
454 if (!text.replace("\r", "")) {
455 if (!text.replace("\r", "")) {
455 // text is nothing (empty string, \r, etc.)
456 // text is nothing (empty string, \r, etc.)
456 // so don't append any elements, which might add undesirable space
457 // so don't append any elements, which might add undesirable space
457 return;
458 return;
458 }
459 }
459
460
460 // If we got here, attach a new div
461 // If we got here, attach a new div
461 var toinsert = this.create_output_area();
462 var toinsert = this.create_output_area();
462 this.append_text(text, {}, toinsert, "output_stream "+subclass);
463 this.append_text(text, {}, toinsert, "output_stream "+subclass);
463 this._safe_append(toinsert);
464 this._safe_append(toinsert);
464 };
465 };
465
466
466
467
467 OutputArea.prototype.append_display_data = function (json) {
468 OutputArea.prototype.append_display_data = function (json) {
468 var toinsert = this.create_output_area();
469 var toinsert = this.create_output_area();
469 if (this.append_mime_type(json, toinsert)) {
470 if (this.append_mime_type(json, toinsert)) {
470 this._safe_append(toinsert);
471 this._safe_append(toinsert);
471 // If we just output latex, typeset it.
472 // If we just output latex, typeset it.
472 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
473 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
473 this.typeset();
474 this.typeset();
474 }
475 }
475 }
476 }
476 };
477 };
477
478
478
479
479 OutputArea.safe_outputs = {
480 OutputArea.safe_outputs = {
480 'text/plain' : true,
481 'text/plain' : true,
481 'image/png' : true,
482 'image/png' : true,
482 'image/jpeg' : true
483 'image/jpeg' : true
483 };
484 };
484
485
485 OutputArea.prototype.append_mime_type = function (json, element) {
486 OutputArea.prototype.append_mime_type = function (json, element) {
486 for (var type_i in OutputArea.display_order) {
487 for (var type_i in OutputArea.display_order) {
487 var type = OutputArea.display_order[type_i];
488 var type = OutputArea.display_order[type_i];
488 var append = OutputArea.append_map[type];
489 var append = OutputArea.append_map[type];
489 if ((json[type] !== undefined) && append) {
490 if ((json[type] !== undefined) && append) {
490 if (!this.trusted && !OutputArea.safe_outputs[type]) {
491 if (!this.trusted && !OutputArea.safe_outputs[type]) {
491 // not trusted show warning and do not display
492 // not trusted show warning and do not display
492 var content = {
493 var content = {
493 text : "Untrusted " + type + " output ignored.",
494 text : "Untrusted " + type + " output ignored.",
494 stream : "stderr"
495 stream : "stderr"
495 }
496 }
496 this.append_stream(content);
497 this.append_stream(content);
497 continue;
498 continue;
498 }
499 }
499 var md = json.metadata || {};
500 var md = json.metadata || {};
500 var toinsert = append.apply(this, [json[type], md, element]);
501 var toinsert = append.apply(this, [json[type], md, element]);
501 $([IPython.events]).trigger('output_appended.OutputArea', [type, json[type], md, toinsert]);
502 $([IPython.events]).trigger('output_appended.OutputArea', [type, json[type], md, toinsert]);
502 return true;
503 return true;
503 }
504 }
504 }
505 }
505 return false;
506 return false;
506 };
507 };
507
508
508
509
509 OutputArea.prototype.append_html = function (html, md, element) {
510 OutputArea.prototype.append_html = function (html, md, element) {
510 var type = 'text/html';
511 var type = 'text/html';
511 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
512 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
512 IPython.keyboard_manager.register_events(toinsert);
513 IPython.keyboard_manager.register_events(toinsert);
513 toinsert.append(html);
514 toinsert.append(html);
514 element.append(toinsert);
515 element.append(toinsert);
515 return toinsert;
516 return toinsert;
516 };
517 };
517
518
518
519
519 OutputArea.prototype.append_javascript = function (js, md, element) {
520 OutputArea.prototype.append_javascript = function (js, md, element) {
520 // We just eval the JS code, element appears in the local scope.
521 // We just eval the JS code, element appears in the local scope.
521 var type = 'application/javascript';
522 var type = 'application/javascript';
522 var toinsert = this.create_output_subarea(md, "output_javascript", type);
523 var toinsert = this.create_output_subarea(md, "output_javascript", type);
523 IPython.keyboard_manager.register_events(toinsert);
524 IPython.keyboard_manager.register_events(toinsert);
524 element.append(toinsert);
525 element.append(toinsert);
525 // FIXME TODO : remove `container element for 3.0`
526 // FIXME TODO : remove `container element for 3.0`
526 //backward compat, js should be eval'ed in a context where `container` is defined.
527 //backward compat, js should be eval'ed in a context where `container` is defined.
527 var container = element;
528 var container = element;
528 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
529 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
529 // end backward compat
530 // end backward compat
530 try {
531 try {
531 eval(js);
532 eval(js);
532 } catch(err) {
533 } catch(err) {
533 console.log(err);
534 console.log(err);
534 this._append_javascript_error(err, toinsert);
535 this._append_javascript_error(err, toinsert);
535 }
536 }
536 return toinsert;
537 return toinsert;
537 };
538 };
538
539
539
540
540 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
541 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
541 var type = 'text/plain';
542 var type = 'text/plain';
542 var toinsert = this.create_output_subarea(md, "output_text", type);
543 var toinsert = this.create_output_subarea(md, "output_text", type);
543 // escape ANSI & HTML specials in plaintext:
544 // escape ANSI & HTML specials in plaintext:
544 data = utils.fixConsole(data);
545 data = utils.fixConsole(data);
545 data = utils.fixCarriageReturn(data);
546 data = utils.fixCarriageReturn(data);
546 data = utils.autoLinkUrls(data);
547 data = utils.autoLinkUrls(data);
547 if (extra_class){
548 if (extra_class){
548 toinsert.addClass(extra_class);
549 toinsert.addClass(extra_class);
549 }
550 }
550 toinsert.append($("<pre/>").html(data));
551 toinsert.append($("<pre/>").html(data));
551 element.append(toinsert);
552 element.append(toinsert);
552 return toinsert;
553 return toinsert;
553 };
554 };
554
555
555
556
556 OutputArea.prototype.append_svg = function (svg, md, element) {
557 OutputArea.prototype.append_svg = function (svg, md, element) {
557 var type = 'image/svg+xml';
558 var type = 'image/svg+xml';
558 var toinsert = this.create_output_subarea(md, "output_svg", type);
559 var toinsert = this.create_output_subarea(md, "output_svg", type);
559 toinsert.append(svg);
560 toinsert.append(svg);
560 element.append(toinsert);
561 element.append(toinsert);
561 return toinsert;
562 return toinsert;
562 };
563 };
563
564
564
565
565 OutputArea.prototype._dblclick_to_reset_size = function (img) {
566 OutputArea.prototype._dblclick_to_reset_size = function (img) {
566 // schedule wrapping image in resizable after a delay,
567 // schedule wrapping image in resizable after a delay,
567 // so we don't end up calling resize on a zero-size object
568 // so we don't end up calling resize on a zero-size object
568 var that = this;
569 var that = this;
569 setTimeout(function () {
570 setTimeout(function () {
570 var h0 = img.height();
571 var h0 = img.height();
571 var w0 = img.width();
572 var w0 = img.width();
572 if (!(h0 && w0)) {
573 if (!(h0 && w0)) {
573 // zero size, schedule another timeout
574 // zero size, schedule another timeout
574 that._dblclick_to_reset_size(img);
575 that._dblclick_to_reset_size(img);
575 return;
576 return;
576 }
577 }
577 img.resizable({
578 img.resizable({
578 aspectRatio: true,
579 aspectRatio: true,
579 autoHide: true
580 autoHide: true
580 });
581 });
581 img.dblclick(function () {
582 img.dblclick(function () {
582 // resize wrapper & image together for some reason:
583 // resize wrapper & image together for some reason:
583 img.parent().height(h0);
584 img.parent().height(h0);
584 img.height(h0);
585 img.height(h0);
585 img.parent().width(w0);
586 img.parent().width(w0);
586 img.width(w0);
587 img.width(w0);
587 });
588 });
588 }, 250);
589 }, 250);
589 };
590 };
590
591
591 var set_width_height = function (img, md, mime) {
592 var set_width_height = function (img, md, mime) {
592 // set width and height of an img element from metadata
593 // set width and height of an img element from metadata
593 var height = _get_metadata_key(md, 'height', mime);
594 var height = _get_metadata_key(md, 'height', mime);
594 if (height !== undefined) img.attr('height', height);
595 if (height !== undefined) img.attr('height', height);
595 var width = _get_metadata_key(md, 'width', mime);
596 var width = _get_metadata_key(md, 'width', mime);
596 if (width !== undefined) img.attr('width', width);
597 if (width !== undefined) img.attr('width', width);
597 };
598 };
598
599
599 OutputArea.prototype.append_png = function (png, md, element) {
600 OutputArea.prototype.append_png = function (png, md, element) {
600 var type = 'image/png';
601 var type = 'image/png';
601 var toinsert = this.create_output_subarea(md, "output_png", type);
602 var toinsert = this.create_output_subarea(md, "output_png", type);
602 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
603 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
603 set_width_height(img, md, 'image/png');
604 set_width_height(img, md, 'image/png');
604 this._dblclick_to_reset_size(img);
605 this._dblclick_to_reset_size(img);
605 toinsert.append(img);
606 toinsert.append(img);
606 element.append(toinsert);
607 element.append(toinsert);
607 return toinsert;
608 return toinsert;
608 };
609 };
609
610
610
611
611 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
612 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
612 var type = 'image/jpeg';
613 var type = 'image/jpeg';
613 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
614 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
614 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
615 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
615 set_width_height(img, md, 'image/jpeg');
616 set_width_height(img, md, 'image/jpeg');
616 this._dblclick_to_reset_size(img);
617 this._dblclick_to_reset_size(img);
617 toinsert.append(img);
618 toinsert.append(img);
618 element.append(toinsert);
619 element.append(toinsert);
619 return toinsert;
620 return toinsert;
620 };
621 };
621
622
622
623
624 OutputArea.prototype.append_pdf = function (pdf, md, element) {
625 var type = 'application/pdf';
626 var toinsert = this.create_output_subarea(md, "output_pdf", type);
627 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
628 a.attr('target', '_blank');
629 a.text('View PDF')
630 toinsert.append(a);
631 element.append(toinsert);
632 return toinsert;
633 }
634
623 OutputArea.prototype.append_latex = function (latex, md, element) {
635 OutputArea.prototype.append_latex = function (latex, md, element) {
624 // This method cannot do the typesetting because the latex first has to
636 // This method cannot do the typesetting because the latex first has to
625 // be on the page.
637 // be on the page.
626 var type = 'text/latex';
638 var type = 'text/latex';
627 var toinsert = this.create_output_subarea(md, "output_latex", type);
639 var toinsert = this.create_output_subarea(md, "output_latex", type);
628 toinsert.append(latex);
640 toinsert.append(latex);
629 element.append(toinsert);
641 element.append(toinsert);
630 return toinsert;
642 return toinsert;
631 };
643 };
632
644
633
645
634 OutputArea.prototype.append_raw_input = function (msg) {
646 OutputArea.prototype.append_raw_input = function (msg) {
635 var that = this;
647 var that = this;
636 this.expand();
648 this.expand();
637 var content = msg.content;
649 var content = msg.content;
638 var area = this.create_output_area();
650 var area = this.create_output_area();
639
651
640 // disable any other raw_inputs, if they are left around
652 // disable any other raw_inputs, if they are left around
641 $("div.output_subarea.raw_input").remove();
653 $("div.output_subarea.raw_input").remove();
642
654
643 area.append(
655 area.append(
644 $("<div/>")
656 $("<div/>")
645 .addClass("box-flex1 output_subarea raw_input")
657 .addClass("box-flex1 output_subarea raw_input")
646 .append(
658 .append(
647 $("<span/>")
659 $("<span/>")
648 .addClass("input_prompt")
660 .addClass("input_prompt")
649 .text(content.prompt)
661 .text(content.prompt)
650 )
662 )
651 .append(
663 .append(
652 $("<input/>")
664 $("<input/>")
653 .addClass("raw_input")
665 .addClass("raw_input")
654 .attr('type', 'text')
666 .attr('type', 'text')
655 .attr("size", 47)
667 .attr("size", 47)
656 .keydown(function (event, ui) {
668 .keydown(function (event, ui) {
657 // make sure we submit on enter,
669 // make sure we submit on enter,
658 // and don't re-execute the *cell* on shift-enter
670 // and don't re-execute the *cell* on shift-enter
659 if (event.which === utils.keycodes.ENTER) {
671 if (event.which === utils.keycodes.ENTER) {
660 that._submit_raw_input();
672 that._submit_raw_input();
661 return false;
673 return false;
662 }
674 }
663 })
675 })
664 )
676 )
665 );
677 );
666
678
667 this.element.append(area);
679 this.element.append(area);
668 var raw_input = area.find('input.raw_input');
680 var raw_input = area.find('input.raw_input');
669 // Register events that enable/disable the keyboard manager while raw
681 // Register events that enable/disable the keyboard manager while raw
670 // input is focused.
682 // input is focused.
671 IPython.keyboard_manager.register_events(raw_input);
683 IPython.keyboard_manager.register_events(raw_input);
672 // Note, the following line used to read raw_input.focus().focus().
684 // Note, the following line used to read raw_input.focus().focus().
673 // This seemed to be needed otherwise only the cell would be focused.
685 // This seemed to be needed otherwise only the cell would be focused.
674 // But with the modal UI, this seems to work fine with one call to focus().
686 // But with the modal UI, this seems to work fine with one call to focus().
675 raw_input.focus();
687 raw_input.focus();
676 }
688 }
677
689
678 OutputArea.prototype._submit_raw_input = function (evt) {
690 OutputArea.prototype._submit_raw_input = function (evt) {
679 var container = this.element.find("div.raw_input");
691 var container = this.element.find("div.raw_input");
680 var theprompt = container.find("span.input_prompt");
692 var theprompt = container.find("span.input_prompt");
681 var theinput = container.find("input.raw_input");
693 var theinput = container.find("input.raw_input");
682 var value = theinput.val();
694 var value = theinput.val();
683 var content = {
695 var content = {
684 output_type : 'stream',
696 output_type : 'stream',
685 name : 'stdout',
697 name : 'stdout',
686 text : theprompt.text() + value + '\n'
698 text : theprompt.text() + value + '\n'
687 }
699 }
688 // remove form container
700 // remove form container
689 container.parent().remove();
701 container.parent().remove();
690 // replace with plaintext version in stdout
702 // replace with plaintext version in stdout
691 this.append_output(content, false);
703 this.append_output(content, false);
692 $([IPython.events]).trigger('send_input_reply.Kernel', value);
704 $([IPython.events]).trigger('send_input_reply.Kernel', value);
693 }
705 }
694
706
695
707
696 OutputArea.prototype.handle_clear_output = function (msg) {
708 OutputArea.prototype.handle_clear_output = function (msg) {
697 // msg spec v4 had stdout, stderr, display keys
709 // msg spec v4 had stdout, stderr, display keys
698 // v4.1 replaced these with just wait
710 // v4.1 replaced these with just wait
699 // The default behavior is the same (stdout=stderr=display=True, wait=False),
711 // The default behavior is the same (stdout=stderr=display=True, wait=False),
700 // so v4 messages will still be properly handled,
712 // so v4 messages will still be properly handled,
701 // except for the rarely used clearing less than all output.
713 // except for the rarely used clearing less than all output.
702 this.clear_output(msg.content.wait || false);
714 this.clear_output(msg.content.wait || false);
703 };
715 };
704
716
705
717
706 OutputArea.prototype.clear_output = function(wait) {
718 OutputArea.prototype.clear_output = function(wait) {
707 if (wait) {
719 if (wait) {
708
720
709 // If a clear is queued, clear before adding another to the queue.
721 // If a clear is queued, clear before adding another to the queue.
710 if (this.clear_queued) {
722 if (this.clear_queued) {
711 this.clear_output(false);
723 this.clear_output(false);
712 };
724 };
713
725
714 this.clear_queued = true;
726 this.clear_queued = true;
715 } else {
727 } else {
716
728
717 // Fix the output div's height if the clear_output is waiting for
729 // Fix the output div's height if the clear_output is waiting for
718 // new output (it is being used in an animation).
730 // new output (it is being used in an animation).
719 if (this.clear_queued) {
731 if (this.clear_queued) {
720 var height = this.element.height();
732 var height = this.element.height();
721 this.element.height(height);
733 this.element.height(height);
722 this.clear_queued = false;
734 this.clear_queued = false;
723 }
735 }
724
736
725 // clear all, no need for logic
737 // clear all, no need for logic
726 this.element.html("");
738 this.element.html("");
727 this.outputs = [];
739 this.outputs = [];
728 this.trusted = true;
740 this.trusted = true;
729 this.unscroll_area();
741 this.unscroll_area();
730 return;
742 return;
731 };
743 };
732 };
744 };
733
745
734
746
735 // JSON serialization
747 // JSON serialization
736
748
737 OutputArea.prototype.fromJSON = function (outputs) {
749 OutputArea.prototype.fromJSON = function (outputs) {
738 var len = outputs.length;
750 var len = outputs.length;
739 var data;
751 var data;
740
752
741 for (var i=0; i<len; i++) {
753 for (var i=0; i<len; i++) {
742 data = outputs[i];
754 data = outputs[i];
743 var msg_type = data.output_type;
755 var msg_type = data.output_type;
744 if (msg_type === "display_data" || msg_type === "pyout") {
756 if (msg_type === "display_data" || msg_type === "pyout") {
745 // convert short keys to mime keys
757 // convert short keys to mime keys
746 // TODO: remove mapping of short keys when we update to nbformat 4
758 // TODO: remove mapping of short keys when we update to nbformat 4
747 data = this.rename_keys(data, OutputArea.mime_map_r);
759 data = this.rename_keys(data, OutputArea.mime_map_r);
748 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
760 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
749 }
761 }
750
762
751 this.append_output(data);
763 this.append_output(data);
752 }
764 }
753 };
765 };
754
766
755
767
756 OutputArea.prototype.toJSON = function () {
768 OutputArea.prototype.toJSON = function () {
757 var outputs = [];
769 var outputs = [];
758 var len = this.outputs.length;
770 var len = this.outputs.length;
759 var data;
771 var data;
760 for (var i=0; i<len; i++) {
772 for (var i=0; i<len; i++) {
761 data = this.outputs[i];
773 data = this.outputs[i];
762 var msg_type = data.output_type;
774 var msg_type = data.output_type;
763 if (msg_type === "display_data" || msg_type === "pyout") {
775 if (msg_type === "display_data" || msg_type === "pyout") {
764 // convert mime keys to short keys
776 // convert mime keys to short keys
765 data = this.rename_keys(data, OutputArea.mime_map);
777 data = this.rename_keys(data, OutputArea.mime_map);
766 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
778 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
767 }
779 }
768 outputs[i] = data;
780 outputs[i] = data;
769 }
781 }
770 return outputs;
782 return outputs;
771 };
783 };
772
784
773 /**
785 /**
774 * Class properties
786 * Class properties
775 **/
787 **/
776
788
777 /**
789 /**
778 * Threshold to trigger autoscroll when the OutputArea is resized,
790 * Threshold to trigger autoscroll when the OutputArea is resized,
779 * typically when new outputs are added.
791 * typically when new outputs are added.
780 *
792 *
781 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
793 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
782 * unless it is < 0, in which case autoscroll will never be triggered
794 * unless it is < 0, in which case autoscroll will never be triggered
783 *
795 *
784 * @property auto_scroll_threshold
796 * @property auto_scroll_threshold
785 * @type Number
797 * @type Number
786 * @default 100
798 * @default 100
787 *
799 *
788 **/
800 **/
789 OutputArea.auto_scroll_threshold = 100;
801 OutputArea.auto_scroll_threshold = 100;
790
802
791 /**
803 /**
792 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
804 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
793 * shorter than this are never scrolled.
805 * shorter than this are never scrolled.
794 *
806 *
795 * @property minimum_scroll_threshold
807 * @property minimum_scroll_threshold
796 * @type Number
808 * @type Number
797 * @default 20
809 * @default 20
798 *
810 *
799 **/
811 **/
800 OutputArea.minimum_scroll_threshold = 20;
812 OutputArea.minimum_scroll_threshold = 20;
801
813
802
814
803
815
804 OutputArea.mime_map = {
816 OutputArea.mime_map = {
805 "text/plain" : "text",
817 "text/plain" : "text",
806 "text/html" : "html",
818 "text/html" : "html",
807 "image/svg+xml" : "svg",
819 "image/svg+xml" : "svg",
808 "image/png" : "png",
820 "image/png" : "png",
809 "image/jpeg" : "jpeg",
821 "image/jpeg" : "jpeg",
822 "application/pdf" : "pdf",
810 "text/latex" : "latex",
823 "text/latex" : "latex",
811 "application/json" : "json",
824 "application/json" : "json",
812 "application/javascript" : "javascript",
825 "application/javascript" : "javascript",
813 };
826 };
814
827
815 OutputArea.mime_map_r = {
828 OutputArea.mime_map_r = {
816 "text" : "text/plain",
829 "text" : "text/plain",
817 "html" : "text/html",
830 "html" : "text/html",
818 "svg" : "image/svg+xml",
831 "svg" : "image/svg+xml",
819 "png" : "image/png",
832 "png" : "image/png",
820 "jpeg" : "image/jpeg",
833 "jpeg" : "image/jpeg",
834 "pdf" : "application/pdf",
821 "latex" : "text/latex",
835 "latex" : "text/latex",
822 "json" : "application/json",
836 "json" : "application/json",
823 "javascript" : "application/javascript",
837 "javascript" : "application/javascript",
824 };
838 };
825
839
826 OutputArea.display_order = [
840 OutputArea.display_order = [
827 'application/javascript',
841 'application/javascript',
828 'text/html',
842 'text/html',
829 'text/latex',
843 'text/latex',
830 'image/svg+xml',
844 'image/svg+xml',
831 'image/png',
845 'image/png',
832 'image/jpeg',
846 'image/jpeg',
847 'application/pdf',
833 'text/plain'
848 'text/plain'
834 ];
849 ];
835
850
836 OutputArea.append_map = {
851 OutputArea.append_map = {
837 "text/plain" : OutputArea.prototype.append_text,
852 "text/plain" : OutputArea.prototype.append_text,
838 "text/html" : OutputArea.prototype.append_html,
853 "text/html" : OutputArea.prototype.append_html,
839 "image/svg+xml" : OutputArea.prototype.append_svg,
854 "image/svg+xml" : OutputArea.prototype.append_svg,
840 "image/png" : OutputArea.prototype.append_png,
855 "image/png" : OutputArea.prototype.append_png,
841 "image/jpeg" : OutputArea.prototype.append_jpeg,
856 "image/jpeg" : OutputArea.prototype.append_jpeg,
842 "text/latex" : OutputArea.prototype.append_latex,
857 "text/latex" : OutputArea.prototype.append_latex,
843 "application/json" : OutputArea.prototype.append_json,
858 "application/json" : OutputArea.prototype.append_json,
844 "application/javascript" : OutputArea.prototype.append_javascript,
859 "application/javascript" : OutputArea.prototype.append_javascript,
860 "application/pdf" : OutputArea.prototype.append_pdf
845 };
861 };
846
862
847 IPython.OutputArea = OutputArea;
863 IPython.OutputArea = OutputArea;
848
864
849 return IPython;
865 return IPython;
850
866
851 }(IPython));
867 }(IPython));
@@ -1,173 +1,173 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // SaveWidget
9 // SaveWidget
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var SaveWidget = function (selector) {
17 var SaveWidget = function (selector) {
18 this.selector = selector;
18 this.selector = selector;
19 if (this.selector !== undefined) {
19 if (this.selector !== undefined) {
20 this.element = $(selector);
20 this.element = $(selector);
21 this.style();
21 this.style();
22 this.bind_events();
22 this.bind_events();
23 }
23 }
24 };
24 };
25
25
26
26
27 SaveWidget.prototype.style = function () {
27 SaveWidget.prototype.style = function () {
28 };
28 };
29
29
30
30
31 SaveWidget.prototype.bind_events = function () {
31 SaveWidget.prototype.bind_events = function () {
32 var that = this;
32 var that = this;
33 this.element.find('span#notebook_name').click(function () {
33 this.element.find('span#notebook_name').click(function () {
34 that.rename_notebook();
34 that.rename_notebook();
35 });
35 });
36 this.element.find('span#notebook_name').hover(function () {
36 this.element.find('span#notebook_name').hover(function () {
37 $(this).addClass("ui-state-hover");
37 $(this).addClass("ui-state-hover");
38 }, function () {
38 }, function () {
39 $(this).removeClass("ui-state-hover");
39 $(this).removeClass("ui-state-hover");
40 });
40 });
41 $([IPython.events]).on('notebook_loaded.Notebook', function () {
41 $([IPython.events]).on('notebook_loaded.Notebook', function () {
42 that.update_notebook_name();
42 that.update_notebook_name();
43 that.update_document_title();
43 that.update_document_title();
44 });
44 });
45 $([IPython.events]).on('notebook_saved.Notebook', function () {
45 $([IPython.events]).on('notebook_saved.Notebook', function () {
46 that.update_notebook_name();
46 that.update_notebook_name();
47 that.update_document_title();
47 that.update_document_title();
48 });
48 });
49 $([IPython.events]).on('notebook_renamed.Notebook', function () {
49 $([IPython.events]).on('notebook_renamed.Notebook', function () {
50 that.update_notebook_name();
50 that.update_notebook_name();
51 that.update_document_title();
51 that.update_document_title();
52 that.update_address_bar();
52 that.update_address_bar();
53 });
53 });
54 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
54 $([IPython.events]).on('notebook_save_failed.Notebook', function () {
55 that.set_save_status('Autosave Failed!');
55 that.set_save_status('Autosave Failed!');
56 });
56 });
57 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
57 $([IPython.events]).on('checkpoints_listed.Notebook', function (event, data) {
58 that.set_last_checkpoint(data[0]);
58 that.set_last_checkpoint(data[0]);
59 });
59 });
60
60
61 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
61 $([IPython.events]).on('checkpoint_created.Notebook', function (event, data) {
62 that.set_last_checkpoint(data);
62 that.set_last_checkpoint(data);
63 });
63 });
64 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
64 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
65 that.set_autosaved(data.value);
65 that.set_autosaved(data.value);
66 });
66 });
67 };
67 };
68
68
69
69
70 SaveWidget.prototype.rename_notebook = function () {
70 SaveWidget.prototype.rename_notebook = function () {
71 var that = this;
71 var that = this;
72 var dialog = $('<div/>').append(
72 var dialog = $('<div/>').append(
73 $("<p/>").addClass("rename-message")
73 $("<p/>").addClass("rename-message")
74 .text('Enter a new notebook name:')
74 .text('Enter a new notebook name:')
75 ).append(
75 ).append(
76 $("<br/>")
76 $("<br/>")
77 ).append(
77 ).append(
78 $('<input/>').attr('type','text').attr('size','25')
78 $('<input/>').attr('type','text').attr('size','25')
79 .val(IPython.notebook.get_notebook_name())
79 .val(IPython.notebook.get_notebook_name())
80 );
80 );
81 IPython.dialog.modal({
81 IPython.dialog.modal({
82 title: "Rename Notebook",
82 title: "Rename Notebook",
83 body: dialog,
83 body: dialog,
84 buttons : {
84 buttons : {
85 "Cancel": {},
85 "Cancel": {},
86 "OK": {
86 "OK": {
87 class: "btn-primary",
87 class: "btn-primary",
88 click: function () {
88 click: function () {
89 var new_name = $(this).find('input').val();
89 var new_name = $(this).find('input').val();
90 if (!IPython.notebook.test_notebook_name(new_name)) {
90 if (!IPython.notebook.test_notebook_name(new_name)) {
91 $(this).find('.rename-message').text(
91 $(this).find('.rename-message').text(
92 "Invalid notebook name. Notebook names must "+
92 "Invalid notebook name. Notebook names must "+
93 "have 1 or more characters and can contain any characters " +
93 "have 1 or more characters and can contain any characters " +
94 "except :/\\. Please enter a new notebook name:"
94 "except :/\\. Please enter a new notebook name:"
95 );
95 );
96 return false;
96 return false;
97 } else {
97 } else {
98 IPython.notebook.rename(new_name);
98 IPython.notebook.rename(new_name);
99 }
99 }
100 }}
100 }}
101 },
101 },
102 open : function (event, ui) {
102 open : function (event, ui) {
103 var that = $(this);
103 var that = $(this);
104 // Upon ENTER, click the OK button.
104 // Upon ENTER, click the OK button.
105 that.find('input[type="text"]').keydown(function (event, ui) {
105 that.find('input[type="text"]').keydown(function (event, ui) {
106 if (event.which === utils.keycodes.ENTER) {
106 if (event.which === utils.keycodes.ENTER) {
107 that.find('.btn-primary').first().click();
107 that.find('.btn-primary').first().click();
108 return false;
108 return false;
109 }
109 }
110 });
110 });
111 that.find('input[type="text"]').focus().select();
111 that.find('input[type="text"]').focus().select();
112 }
112 }
113 });
113 });
114 }
114 }
115
115
116
116
117 SaveWidget.prototype.update_notebook_name = function () {
117 SaveWidget.prototype.update_notebook_name = function () {
118 var nbname = IPython.notebook.get_notebook_name();
118 var nbname = IPython.notebook.get_notebook_name();
119 this.element.find('span#notebook_name').text(nbname);
119 this.element.find('span#notebook_name').text(nbname);
120 };
120 };
121
121
122
122
123 SaveWidget.prototype.update_document_title = function () {
123 SaveWidget.prototype.update_document_title = function () {
124 var nbname = IPython.notebook.get_notebook_name();
124 var nbname = IPython.notebook.get_notebook_name();
125 document.title = nbname;
125 document.title = nbname;
126 };
126 };
127
127
128 SaveWidget.prototype.update_address_bar = function(){
128 SaveWidget.prototype.update_address_bar = function(){
129 var nbname = IPython.notebook.notebook_name;
129 var nbname = IPython.notebook.notebook_name;
130 var path = IPython.notebook.notebookPath();
130 var path = IPython.notebook.notebook_path;
131 var state = {path : utils.url_join_encode(path, nbname)};
131 var state = {path : utils.url_join_encode(path, nbname)};
132 window.history.replaceState(state, "", utils.url_join_encode(
132 window.history.replaceState(state, "", utils.url_join_encode(
133 "/notebooks",
133 "/notebooks",
134 path,
134 path,
135 nbname)
135 nbname)
136 );
136 );
137 }
137 }
138
138
139
139
140 SaveWidget.prototype.set_save_status = function (msg) {
140 SaveWidget.prototype.set_save_status = function (msg) {
141 this.element.find('span#autosave_status').text(msg);
141 this.element.find('span#autosave_status').text(msg);
142 }
142 }
143
143
144 SaveWidget.prototype.set_checkpoint_status = function (msg) {
144 SaveWidget.prototype.set_checkpoint_status = function (msg) {
145 this.element.find('span#checkpoint_status').text(msg);
145 this.element.find('span#checkpoint_status').text(msg);
146 }
146 }
147
147
148 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
148 SaveWidget.prototype.set_last_checkpoint = function (checkpoint) {
149 if (!checkpoint) {
149 if (!checkpoint) {
150 this.set_checkpoint_status("");
150 this.set_checkpoint_status("");
151 return;
151 return;
152 }
152 }
153 var d = new Date(checkpoint.last_modified);
153 var d = new Date(checkpoint.last_modified);
154 this.set_checkpoint_status(
154 this.set_checkpoint_status(
155 "Last Checkpoint: " + d.format('mmm dd HH:MM')
155 "Last Checkpoint: " + d.format('mmm dd HH:MM')
156 );
156 );
157 }
157 }
158
158
159 SaveWidget.prototype.set_autosaved = function (dirty) {
159 SaveWidget.prototype.set_autosaved = function (dirty) {
160 if (dirty) {
160 if (dirty) {
161 this.set_save_status("(unsaved changes)");
161 this.set_save_status("(unsaved changes)");
162 } else {
162 } else {
163 this.set_save_status("(autosaved)");
163 this.set_save_status("(autosaved)");
164 }
164 }
165 };
165 };
166
166
167
167
168 IPython.SaveWidget = SaveWidget;
168 IPython.SaveWidget = SaveWidget;
169
169
170 return IPython;
170 return IPython;
171
171
172 }(IPython));
172 }(IPython));
173
173
@@ -1,3 +1,18 b''
1 #notification_area {
1 #notification_area {
2 z-index: 10;
2 z-index: 10;
3 }
3 }
4
5 .indicator_area {
6 color: @navbarLinkColor;
7 padding: 4px 3px;
8 margin: 0px;
9 width: 11px;
10 z-index: 10;
11 text-align: center;
12 }
13
14 #kernel_indicator {
15 // Pull it to the right, outside the container boundary
16 margin-right: -16px;
17 }
18
@@ -1,22 +1,13 b''
1 .notification_widget{
1 .notification_widget {
2 color: @navbarLinkColor;
2 color: @navbarLinkColor;
3 padding: 1px 12px;
3 padding: 1px 12px;
4 margin: 2px 4px;
4 margin: 2px 4px;
5 z-index: 10;
5 z-index: 10;
6 border: 1px solid #ccc;
6 border: 1px solid #ccc;
7 border-radius: @baseBorderRadius;
7 border-radius: @baseBorderRadius;
8 background: rgba(240, 240, 240, 0.5);
8 background: rgba(240, 240, 240, 0.5);
9
9
10 &.span {
10 &.span {
11 padding-right:2px;
11 padding-right:2px;
12 }
12 }
13
14 }
15
16 #indicator_area{
17 color: @navbarLinkColor;
18 padding: 2px 2px;
19 margin: 2px -9px 2px 4px;
20 z-index: 10;
21
22 }
13 }
@@ -1,603 +1,609 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Kernel
9 // Kernel
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule Kernel
15 * @submodule Kernel
16 */
16 */
17
17
18 var IPython = (function (IPython) {
18 var IPython = (function (IPython) {
19 "use strict";
19 "use strict";
20
20
21 var utils = IPython.utils;
21 var utils = IPython.utils;
22
22
23 // Initialization and connection.
23 // Initialization and connection.
24 /**
24 /**
25 * A Kernel Class to communicate with the Python kernel
25 * A Kernel Class to communicate with the Python kernel
26 * @Class Kernel
26 * @Class Kernel
27 */
27 */
28 var Kernel = function (base_url) {
28 var Kernel = function (kernel_service_url) {
29 this.kernel_id = null;
29 this.kernel_id = null;
30 this.shell_channel = null;
30 this.shell_channel = null;
31 this.iopub_channel = null;
31 this.iopub_channel = null;
32 this.stdin_channel = null;
32 this.stdin_channel = null;
33 this.base_url = base_url;
33 this.kernel_service_url = kernel_service_url;
34 this.running = false;
34 this.running = false;
35 this.username = "username";
35 this.username = "username";
36 this.session_id = utils.uuid();
36 this.session_id = utils.uuid();
37 this._msg_callbacks = {};
37 this._msg_callbacks = {};
38
38
39 if (typeof(WebSocket) !== 'undefined') {
39 if (typeof(WebSocket) !== 'undefined') {
40 this.WebSocket = WebSocket;
40 this.WebSocket = WebSocket;
41 } else if (typeof(MozWebSocket) !== 'undefined') {
41 } else if (typeof(MozWebSocket) !== 'undefined') {
42 this.WebSocket = MozWebSocket;
42 this.WebSocket = MozWebSocket;
43 } else {
43 } else {
44 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox ≥ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
44 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox ≥ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
45 }
45 }
46
46
47 this.bind_events();
47 this.bind_events();
48 this.init_iopub_handlers();
48 this.init_iopub_handlers();
49 this.comm_manager = new IPython.CommManager(this);
49 this.comm_manager = new IPython.CommManager(this);
50 this.widget_manager = new IPython.WidgetManager(this.comm_manager);
50 this.widget_manager = new IPython.WidgetManager(this.comm_manager);
51 };
51 };
52
52
53
53
54 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
54 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
55 var msg = {
55 var msg = {
56 header : {
56 header : {
57 msg_id : utils.uuid(),
57 msg_id : utils.uuid(),
58 username : this.username,
58 username : this.username,
59 session : this.session_id,
59 session : this.session_id,
60 msg_type : msg_type
60 msg_type : msg_type
61 },
61 },
62 metadata : metadata || {},
62 metadata : metadata || {},
63 content : content,
63 content : content,
64 parent_header : {}
64 parent_header : {}
65 };
65 };
66 return msg;
66 return msg;
67 };
67 };
68
68
69 Kernel.prototype.bind_events = function () {
69 Kernel.prototype.bind_events = function () {
70 var that = this;
70 var that = this;
71 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
71 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
72 that.send_input_reply(data);
72 that.send_input_reply(data);
73 });
73 });
74 };
74 };
75
75
76 // Initialize the iopub handlers
76 // Initialize the iopub handlers
77
77
78 Kernel.prototype.init_iopub_handlers = function () {
78 Kernel.prototype.init_iopub_handlers = function () {
79 var output_types = ['stream', 'display_data', 'pyout', 'pyerr'];
79 var output_types = ['stream', 'display_data', 'pyout', 'pyerr'];
80 this._iopub_handlers = {};
80 this._iopub_handlers = {};
81 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
81 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
82 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
82 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
83
83
84 for (var i=0; i < output_types.length; i++) {
84 for (var i=0; i < output_types.length; i++) {
85 this.register_iopub_handler(output_types[i], $.proxy(this._handle_output_message, this));
85 this.register_iopub_handler(output_types[i], $.proxy(this._handle_output_message, this));
86 }
86 }
87 };
87 };
88
88
89 /**
89 /**
90 * Start the Python kernel
90 * Start the Python kernel
91 * @method start
91 * @method start
92 */
92 */
93 Kernel.prototype.start = function (params) {
93 Kernel.prototype.start = function (params) {
94 params = params || {};
94 params = params || {};
95 if (!this.running) {
95 if (!this.running) {
96 var qs = $.param(params);
96 var qs = $.param(params);
97 var url = this.base_url + '?' + qs;
97 $.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
98 $.post(url,
99 $.proxy(this._kernel_started, this),
98 $.proxy(this._kernel_started, this),
100 'json'
99 'json'
101 );
100 );
102 }
101 }
103 };
102 };
104
103
105 /**
104 /**
106 * Restart the python kernel.
105 * Restart the python kernel.
107 *
106 *
108 * Emit a 'status_restarting.Kernel' event with
107 * Emit a 'status_restarting.Kernel' event with
109 * the current object as parameter
108 * the current object as parameter
110 *
109 *
111 * @method restart
110 * @method restart
112 */
111 */
113 Kernel.prototype.restart = function () {
112 Kernel.prototype.restart = function () {
114 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
113 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
115 if (this.running) {
114 if (this.running) {
116 this.stop_channels();
115 this.stop_channels();
117 var url = utils.url_join_encode(this.kernel_url, "restart");
116 $.post(utils.url_join_encode(this.kernel_url, "restart"),
118 $.post(url,
119 $.proxy(this._kernel_started, this),
117 $.proxy(this._kernel_started, this),
120 'json'
118 'json'
121 );
119 );
122 }
120 }
123 };
121 };
124
122
125
123
126 Kernel.prototype._kernel_started = function (json) {
124 Kernel.prototype._kernel_started = function (json) {
127 console.log("Kernel started: ", json.id);
125 console.log("Kernel started: ", json.id);
128 this.running = true;
126 this.running = true;
129 this.kernel_id = json.id;
127 this.kernel_id = json.id;
130 var ws_url = json.ws_url;
128 var ws_url = json.ws_url;
131 if (ws_url.match(/wss?:\/\//) === null) {
129 if (ws_url.match(/wss?:\/\//) === null) {
132 // trailing 's' in https will become wss for secure web sockets
130 // trailing 's' in https will become wss for secure web sockets
133 var prot = location.protocol.replace('http', 'ws') + "//";
131 var prot = location.protocol.replace('http', 'ws') + "//";
134 ws_url = prot + location.host + ws_url;
132 ws_url = prot + location.host + ws_url;
135 }
133 }
136 this.ws_url = ws_url;
134 var parsed = utils.parse_url(ws_url);
137 this.kernel_url = utils.url_join_encode(this.base_url, this.kernel_id);
135 this.ws_host = parsed.protocol + "//" + parsed.host;
136 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
137 this.ws_url = utils.url_path_join(parsed.pathname, this.kernel_url);
138 this.start_channels();
138 this.start_channels();
139 };
139 };
140
140
141
141
142 Kernel.prototype._websocket_closed = function(ws_url, early) {
142 Kernel.prototype._websocket_closed = function(ws_url, early) {
143 this.stop_channels();
143 this.stop_channels();
144 $([IPython.events]).trigger('websocket_closed.Kernel',
144 $([IPython.events]).trigger('websocket_closed.Kernel',
145 {ws_url: ws_url, kernel: this, early: early}
145 {ws_url: ws_url, kernel: this, early: early}
146 );
146 );
147 };
147 };
148
148
149 /**
149 /**
150 * Start the `shell`and `iopub` channels.
150 * Start the `shell`and `iopub` channels.
151 * Will stop and restart them if they already exist.
151 * Will stop and restart them if they already exist.
152 *
152 *
153 * @method start_channels
153 * @method start_channels
154 */
154 */
155 Kernel.prototype.start_channels = function () {
155 Kernel.prototype.start_channels = function () {
156 var that = this;
156 var that = this;
157 this.stop_channels();
157 this.stop_channels();
158 var ws_url = this.ws_url + this.kernel_url;
158 console.log("Starting WebSockets:", this.ws_host + this.ws_url);
159 console.log("Starting WebSockets:", ws_url);
159 this.shell_channel = new this.WebSocket(
160 this.shell_channel = new this.WebSocket(ws_url + "/shell");
160 this.ws_host + utils.url_join_encode(this.ws_url, "shell")
161 this.stdin_channel = new this.WebSocket(ws_url + "/stdin");
161 );
162 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
162 this.stdin_channel = new this.WebSocket(
163 this.ws_host + utils.url_join_encode(this.ws_url, "stdin")
164 );
165 this.iopub_channel = new this.WebSocket(
166 this.ws_host + utils.url_join_encode(this.ws_url, "iopub")
167 );
163
168
169 var ws_host_url = this.ws_host + this.ws_url;
164 var already_called_onclose = false; // only alert once
170 var already_called_onclose = false; // only alert once
165 var ws_closed_early = function(evt){
171 var ws_closed_early = function(evt){
166 if (already_called_onclose){
172 if (already_called_onclose){
167 return;
173 return;
168 }
174 }
169 already_called_onclose = true;
175 already_called_onclose = true;
170 if ( ! evt.wasClean ){
176 if ( ! evt.wasClean ){
171 that._websocket_closed(ws_url, true);
177 that._websocket_closed(ws_host_url, true);
172 }
178 }
173 };
179 };
174 var ws_closed_late = function(evt){
180 var ws_closed_late = function(evt){
175 if (already_called_onclose){
181 if (already_called_onclose){
176 return;
182 return;
177 }
183 }
178 already_called_onclose = true;
184 already_called_onclose = true;
179 if ( ! evt.wasClean ){
185 if ( ! evt.wasClean ){
180 that._websocket_closed(ws_url, false);
186 that._websocket_closed(ws_host_url, false);
181 }
187 }
182 };
188 };
183 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
189 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
184 for (var i=0; i < channels.length; i++) {
190 for (var i=0; i < channels.length; i++) {
185 channels[i].onopen = $.proxy(this._ws_opened, this);
191 channels[i].onopen = $.proxy(this._ws_opened, this);
186 channels[i].onclose = ws_closed_early;
192 channels[i].onclose = ws_closed_early;
187 }
193 }
188 // switch from early-close to late-close message after 1s
194 // switch from early-close to late-close message after 1s
189 setTimeout(function() {
195 setTimeout(function() {
190 for (var i=0; i < channels.length; i++) {
196 for (var i=0; i < channels.length; i++) {
191 if (channels[i] !== null) {
197 if (channels[i] !== null) {
192 channels[i].onclose = ws_closed_late;
198 channels[i].onclose = ws_closed_late;
193 }
199 }
194 }
200 }
195 }, 1000);
201 }, 1000);
196 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
202 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
197 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
203 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
198 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
204 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
199 };
205 };
200
206
201 /**
207 /**
202 * Handle a websocket entering the open state
208 * Handle a websocket entering the open state
203 * sends session and cookie authentication info as first message.
209 * sends session and cookie authentication info as first message.
204 * Once all sockets are open, signal the Kernel.status_started event.
210 * Once all sockets are open, signal the Kernel.status_started event.
205 * @method _ws_opened
211 * @method _ws_opened
206 */
212 */
207 Kernel.prototype._ws_opened = function (evt) {
213 Kernel.prototype._ws_opened = function (evt) {
208 // send the session id so the Session object Python-side
214 // send the session id so the Session object Python-side
209 // has the same identity
215 // has the same identity
210 evt.target.send(this.session_id + ':' + document.cookie);
216 evt.target.send(this.session_id + ':' + document.cookie);
211
217
212 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
218 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
213 for (var i=0; i < channels.length; i++) {
219 for (var i=0; i < channels.length; i++) {
214 // if any channel is not ready, don't trigger event.
220 // if any channel is not ready, don't trigger event.
215 if ( !channels[i].readyState ) return;
221 if ( !channels[i].readyState ) return;
216 }
222 }
217 // all events ready, trigger started event.
223 // all events ready, trigger started event.
218 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
224 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
219 };
225 };
220
226
221 /**
227 /**
222 * Stop the websocket channels.
228 * Stop the websocket channels.
223 * @method stop_channels
229 * @method stop_channels
224 */
230 */
225 Kernel.prototype.stop_channels = function () {
231 Kernel.prototype.stop_channels = function () {
226 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
232 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
227 for (var i=0; i < channels.length; i++) {
233 for (var i=0; i < channels.length; i++) {
228 if ( channels[i] !== null ) {
234 if ( channels[i] !== null ) {
229 channels[i].onclose = null;
235 channels[i].onclose = null;
230 channels[i].close();
236 channels[i].close();
231 }
237 }
232 }
238 }
233 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
239 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
234 };
240 };
235
241
236 // Main public methods.
242 // Main public methods.
237
243
238 // send a message on the Kernel's shell channel
244 // send a message on the Kernel's shell channel
239 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
245 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
240 var msg = this._get_msg(msg_type, content, metadata);
246 var msg = this._get_msg(msg_type, content, metadata);
241 this.shell_channel.send(JSON.stringify(msg));
247 this.shell_channel.send(JSON.stringify(msg));
242 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
248 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
243 return msg.header.msg_id;
249 return msg.header.msg_id;
244 };
250 };
245
251
246 /**
252 /**
247 * Get kernel info
253 * Get kernel info
248 *
254 *
249 * @param callback {function}
255 * @param callback {function}
250 * @method object_info
256 * @method object_info
251 *
257 *
252 * When calling this method, pass a callback function that expects one argument.
258 * When calling this method, pass a callback function that expects one argument.
253 * The callback will be passed the complete `kernel_info_reply` message documented
259 * The callback will be passed the complete `kernel_info_reply` message documented
254 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
260 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
255 */
261 */
256 Kernel.prototype.kernel_info = function (callback) {
262 Kernel.prototype.kernel_info = function (callback) {
257 var callbacks;
263 var callbacks;
258 if (callback) {
264 if (callback) {
259 callbacks = { shell : { reply : callback } };
265 callbacks = { shell : { reply : callback } };
260 }
266 }
261 return this.send_shell_message("kernel_info_request", {}, callbacks);
267 return this.send_shell_message("kernel_info_request", {}, callbacks);
262 };
268 };
263
269
264 /**
270 /**
265 * Get info on an object
271 * Get info on an object
266 *
272 *
267 * @param objname {string}
273 * @param objname {string}
268 * @param callback {function}
274 * @param callback {function}
269 * @method object_info
275 * @method object_info
270 *
276 *
271 * When calling this method, pass a callback function that expects one argument.
277 * When calling this method, pass a callback function that expects one argument.
272 * The callback will be passed the complete `object_info_reply` message documented
278 * The callback will be passed the complete `object_info_reply` message documented
273 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
279 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
274 */
280 */
275 Kernel.prototype.object_info = function (objname, callback) {
281 Kernel.prototype.object_info = function (objname, callback) {
276 var callbacks;
282 var callbacks;
277 if (callback) {
283 if (callback) {
278 callbacks = { shell : { reply : callback } };
284 callbacks = { shell : { reply : callback } };
279 }
285 }
280
286
281 if (typeof(objname) !== null && objname !== null) {
287 if (typeof(objname) !== null && objname !== null) {
282 var content = {
288 var content = {
283 oname : objname.toString(),
289 oname : objname.toString(),
284 detail_level : 0,
290 detail_level : 0,
285 };
291 };
286 return this.send_shell_message("object_info_request", content, callbacks);
292 return this.send_shell_message("object_info_request", content, callbacks);
287 }
293 }
288 return;
294 return;
289 };
295 };
290
296
291 /**
297 /**
292 * Execute given code into kernel, and pass result to callback.
298 * Execute given code into kernel, and pass result to callback.
293 *
299 *
294 * @async
300 * @async
295 * @method execute
301 * @method execute
296 * @param {string} code
302 * @param {string} code
297 * @param [callbacks] {Object} With the following keys (all optional)
303 * @param [callbacks] {Object} With the following keys (all optional)
298 * @param callbacks.shell.reply {function}
304 * @param callbacks.shell.reply {function}
299 * @param callbacks.shell.payload.[payload_name] {function}
305 * @param callbacks.shell.payload.[payload_name] {function}
300 * @param callbacks.iopub.output {function}
306 * @param callbacks.iopub.output {function}
301 * @param callbacks.iopub.clear_output {function}
307 * @param callbacks.iopub.clear_output {function}
302 * @param callbacks.input {function}
308 * @param callbacks.input {function}
303 * @param {object} [options]
309 * @param {object} [options]
304 * @param [options.silent=false] {Boolean}
310 * @param [options.silent=false] {Boolean}
305 * @param [options.user_expressions=empty_dict] {Dict}
311 * @param [options.user_expressions=empty_dict] {Dict}
306 * @param [options.user_variables=empty_list] {List od Strings}
312 * @param [options.user_variables=empty_list] {List od Strings}
307 * @param [options.allow_stdin=false] {Boolean} true|false
313 * @param [options.allow_stdin=false] {Boolean} true|false
308 *
314 *
309 * @example
315 * @example
310 *
316 *
311 * The options object should contain the options for the execute call. Its default
317 * The options object should contain the options for the execute call. Its default
312 * values are:
318 * values are:
313 *
319 *
314 * options = {
320 * options = {
315 * silent : true,
321 * silent : true,
316 * user_variables : [],
322 * user_variables : [],
317 * user_expressions : {},
323 * user_expressions : {},
318 * allow_stdin : false
324 * allow_stdin : false
319 * }
325 * }
320 *
326 *
321 * When calling this method pass a callbacks structure of the form:
327 * When calling this method pass a callbacks structure of the form:
322 *
328 *
323 * callbacks = {
329 * callbacks = {
324 * shell : {
330 * shell : {
325 * reply : execute_reply_callback,
331 * reply : execute_reply_callback,
326 * payload : {
332 * payload : {
327 * set_next_input : set_next_input_callback,
333 * set_next_input : set_next_input_callback,
328 * }
334 * }
329 * },
335 * },
330 * iopub : {
336 * iopub : {
331 * output : output_callback,
337 * output : output_callback,
332 * clear_output : clear_output_callback,
338 * clear_output : clear_output_callback,
333 * },
339 * },
334 * input : raw_input_callback
340 * input : raw_input_callback
335 * }
341 * }
336 *
342 *
337 * Each callback will be passed the entire message as a single arugment.
343 * Each callback will be passed the entire message as a single arugment.
338 * Payload handlers will be passed the corresponding payload and the execute_reply message.
344 * Payload handlers will be passed the corresponding payload and the execute_reply message.
339 */
345 */
340 Kernel.prototype.execute = function (code, callbacks, options) {
346 Kernel.prototype.execute = function (code, callbacks, options) {
341
347
342 var content = {
348 var content = {
343 code : code,
349 code : code,
344 silent : true,
350 silent : true,
345 store_history : false,
351 store_history : false,
346 user_variables : [],
352 user_variables : [],
347 user_expressions : {},
353 user_expressions : {},
348 allow_stdin : false
354 allow_stdin : false
349 };
355 };
350 callbacks = callbacks || {};
356 callbacks = callbacks || {};
351 if (callbacks.input !== undefined) {
357 if (callbacks.input !== undefined) {
352 content.allow_stdin = true;
358 content.allow_stdin = true;
353 }
359 }
354 $.extend(true, content, options);
360 $.extend(true, content, options);
355 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
361 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
356 return this.send_shell_message("execute_request", content, callbacks);
362 return this.send_shell_message("execute_request", content, callbacks);
357 };
363 };
358
364
359 /**
365 /**
360 * When calling this method, pass a function to be called with the `complete_reply` message
366 * When calling this method, pass a function to be called with the `complete_reply` message
361 * as its only argument when it arrives.
367 * as its only argument when it arrives.
362 *
368 *
363 * `complete_reply` is documented
369 * `complete_reply` is documented
364 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
370 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
365 *
371 *
366 * @method complete
372 * @method complete
367 * @param line {integer}
373 * @param line {integer}
368 * @param cursor_pos {integer}
374 * @param cursor_pos {integer}
369 * @param callback {function}
375 * @param callback {function}
370 *
376 *
371 */
377 */
372 Kernel.prototype.complete = function (line, cursor_pos, callback) {
378 Kernel.prototype.complete = function (line, cursor_pos, callback) {
373 var callbacks;
379 var callbacks;
374 if (callback) {
380 if (callback) {
375 callbacks = { shell : { reply : callback } };
381 callbacks = { shell : { reply : callback } };
376 }
382 }
377 var content = {
383 var content = {
378 text : '',
384 text : '',
379 line : line,
385 line : line,
380 block : null,
386 block : null,
381 cursor_pos : cursor_pos
387 cursor_pos : cursor_pos
382 };
388 };
383 return this.send_shell_message("complete_request", content, callbacks);
389 return this.send_shell_message("complete_request", content, callbacks);
384 };
390 };
385
391
386
392
387 Kernel.prototype.interrupt = function () {
393 Kernel.prototype.interrupt = function () {
388 if (this.running) {
394 if (this.running) {
389 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
395 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
390 $.post(this.kernel_url + "/interrupt");
396 $.post(utils.url_join_encode(this.kernel_url, "interrupt"));
391 }
397 }
392 };
398 };
393
399
394
400
395 Kernel.prototype.kill = function () {
401 Kernel.prototype.kill = function () {
396 if (this.running) {
402 if (this.running) {
397 this.running = false;
403 this.running = false;
398 var settings = {
404 var settings = {
399 cache : false,
405 cache : false,
400 type : "DELETE"
406 type : "DELETE"
401 };
407 };
402 $.ajax(this.kernel_url, settings);
408 $.ajax(utils.url_join_encode(this.kernel_url), settings);
403 }
409 }
404 };
410 };
405
411
406 Kernel.prototype.send_input_reply = function (input) {
412 Kernel.prototype.send_input_reply = function (input) {
407 var content = {
413 var content = {
408 value : input,
414 value : input,
409 };
415 };
410 $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
416 $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
411 var msg = this._get_msg("input_reply", content);
417 var msg = this._get_msg("input_reply", content);
412 this.stdin_channel.send(JSON.stringify(msg));
418 this.stdin_channel.send(JSON.stringify(msg));
413 return msg.header.msg_id;
419 return msg.header.msg_id;
414 };
420 };
415
421
416
422
417 // Reply handlers
423 // Reply handlers
418
424
419 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
425 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
420 this._iopub_handlers[msg_type] = callback;
426 this._iopub_handlers[msg_type] = callback;
421 };
427 };
422
428
423 Kernel.prototype.get_iopub_handler = function (msg_type) {
429 Kernel.prototype.get_iopub_handler = function (msg_type) {
424 // get iopub handler for a specific message type
430 // get iopub handler for a specific message type
425 return this._iopub_handlers[msg_type];
431 return this._iopub_handlers[msg_type];
426 };
432 };
427
433
428
434
429 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
435 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
430 // get callbacks for a specific message
436 // get callbacks for a specific message
431 return this._msg_callbacks[msg_id];
437 return this._msg_callbacks[msg_id];
432 };
438 };
433
439
434
440
435 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
441 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
436 if (this._msg_callbacks[msg_id] !== undefined ) {
442 if (this._msg_callbacks[msg_id] !== undefined ) {
437 delete this._msg_callbacks[msg_id];
443 delete this._msg_callbacks[msg_id];
438 }
444 }
439 };
445 };
440
446
441 /* Set callbacks for a particular message.
447 /* Set callbacks for a particular message.
442 * Callbacks should be a struct of the following form:
448 * Callbacks should be a struct of the following form:
443 * shell : {
449 * shell : {
444 *
450 *
445 * }
451 * }
446
452
447 */
453 */
448 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
454 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
449 if (callbacks) {
455 if (callbacks) {
450 // shallow-copy mapping, because we will modify it at the top level
456 // shallow-copy mapping, because we will modify it at the top level
451 var cbcopy = this._msg_callbacks[msg_id] = {};
457 var cbcopy = this._msg_callbacks[msg_id] = {};
452 cbcopy.shell = callbacks.shell;
458 cbcopy.shell = callbacks.shell;
453 cbcopy.iopub = callbacks.iopub;
459 cbcopy.iopub = callbacks.iopub;
454 cbcopy.input = callbacks.input;
460 cbcopy.input = callbacks.input;
455 this._msg_callbacks[msg_id] = cbcopy;
461 this._msg_callbacks[msg_id] = cbcopy;
456 }
462 }
457 };
463 };
458
464
459
465
460 Kernel.prototype._handle_shell_reply = function (e) {
466 Kernel.prototype._handle_shell_reply = function (e) {
461 var reply = $.parseJSON(e.data);
467 var reply = $.parseJSON(e.data);
462 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
468 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
463 var content = reply.content;
469 var content = reply.content;
464 var metadata = reply.metadata;
470 var metadata = reply.metadata;
465 var parent_id = reply.parent_header.msg_id;
471 var parent_id = reply.parent_header.msg_id;
466 var callbacks = this.get_callbacks_for_msg(parent_id);
472 var callbacks = this.get_callbacks_for_msg(parent_id);
467 if (!callbacks || !callbacks.shell) {
473 if (!callbacks || !callbacks.shell) {
468 return;
474 return;
469 }
475 }
470 var shell_callbacks = callbacks.shell;
476 var shell_callbacks = callbacks.shell;
471
477
472 // clear callbacks on shell
478 // clear callbacks on shell
473 delete callbacks.shell;
479 delete callbacks.shell;
474 delete callbacks.input;
480 delete callbacks.input;
475 if (!callbacks.iopub) {
481 if (!callbacks.iopub) {
476 this.clear_callbacks_for_msg(parent_id);
482 this.clear_callbacks_for_msg(parent_id);
477 }
483 }
478
484
479 if (shell_callbacks.reply !== undefined) {
485 if (shell_callbacks.reply !== undefined) {
480 shell_callbacks.reply(reply);
486 shell_callbacks.reply(reply);
481 }
487 }
482 if (content.payload && shell_callbacks.payload) {
488 if (content.payload && shell_callbacks.payload) {
483 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
489 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
484 }
490 }
485 };
491 };
486
492
487
493
488 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
494 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
489 var l = payloads.length;
495 var l = payloads.length;
490 // Payloads are handled by triggering events because we don't want the Kernel
496 // Payloads are handled by triggering events because we don't want the Kernel
491 // to depend on the Notebook or Pager classes.
497 // to depend on the Notebook or Pager classes.
492 for (var i=0; i<l; i++) {
498 for (var i=0; i<l; i++) {
493 var payload = payloads[i];
499 var payload = payloads[i];
494 var callback = payload_callbacks[payload.source];
500 var callback = payload_callbacks[payload.source];
495 if (callback) {
501 if (callback) {
496 callback(payload, msg);
502 callback(payload, msg);
497 }
503 }
498 }
504 }
499 };
505 };
500
506
501 Kernel.prototype._handle_status_message = function (msg) {
507 Kernel.prototype._handle_status_message = function (msg) {
502 var execution_state = msg.content.execution_state;
508 var execution_state = msg.content.execution_state;
503 var parent_id = msg.parent_header.msg_id;
509 var parent_id = msg.parent_header.msg_id;
504
510
505 // dispatch status msg callbacks, if any
511 // dispatch status msg callbacks, if any
506 var callbacks = this.get_callbacks_for_msg(parent_id);
512 var callbacks = this.get_callbacks_for_msg(parent_id);
507 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
513 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
508 try {
514 try {
509 callbacks.iopub.status(msg);
515 callbacks.iopub.status(msg);
510 } catch (e) {
516 } catch (e) {
511 console.log("Exception in status msg handler", e, e.stack);
517 console.log("Exception in status msg handler", e, e.stack);
512 }
518 }
513 }
519 }
514
520
515 if (execution_state === 'busy') {
521 if (execution_state === 'busy') {
516 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
522 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
517 } else if (execution_state === 'idle') {
523 } else if (execution_state === 'idle') {
518 // clear callbacks on idle, there can be no more
524 // clear callbacks on idle, there can be no more
519 if (callbacks !== undefined) {
525 if (callbacks !== undefined) {
520 delete callbacks.iopub;
526 delete callbacks.iopub;
521 delete callbacks.input;
527 delete callbacks.input;
522 if (!callbacks.shell) {
528 if (!callbacks.shell) {
523 this.clear_callbacks_for_msg(parent_id);
529 this.clear_callbacks_for_msg(parent_id);
524 }
530 }
525 }
531 }
526 // trigger status_idle event
532 // trigger status_idle event
527 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
533 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
528 } else if (execution_state === 'restarting') {
534 } else if (execution_state === 'restarting') {
529 // autorestarting is distinct from restarting,
535 // autorestarting is distinct from restarting,
530 // in that it means the kernel died and the server is restarting it.
536 // in that it means the kernel died and the server is restarting it.
531 // status_restarting sets the notification widget,
537 // status_restarting sets the notification widget,
532 // autorestart shows the more prominent dialog.
538 // autorestart shows the more prominent dialog.
533 $([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this});
539 $([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this});
534 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
540 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
535 } else if (execution_state === 'dead') {
541 } else if (execution_state === 'dead') {
536 this.stop_channels();
542 this.stop_channels();
537 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
543 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
538 }
544 }
539 };
545 };
540
546
541
547
542 // handle clear_output message
548 // handle clear_output message
543 Kernel.prototype._handle_clear_output = function (msg) {
549 Kernel.prototype._handle_clear_output = function (msg) {
544 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
550 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
545 if (!callbacks || !callbacks.iopub) {
551 if (!callbacks || !callbacks.iopub) {
546 return;
552 return;
547 }
553 }
548 var callback = callbacks.iopub.clear_output;
554 var callback = callbacks.iopub.clear_output;
549 if (callback) {
555 if (callback) {
550 callback(msg);
556 callback(msg);
551 }
557 }
552 };
558 };
553
559
554
560
555 // handle an output message (pyout, display_data, etc.)
561 // handle an output message (pyout, display_data, etc.)
556 Kernel.prototype._handle_output_message = function (msg) {
562 Kernel.prototype._handle_output_message = function (msg) {
557 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
563 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
558 if (!callbacks || !callbacks.iopub) {
564 if (!callbacks || !callbacks.iopub) {
559 return;
565 return;
560 }
566 }
561 var callback = callbacks.iopub.output;
567 var callback = callbacks.iopub.output;
562 if (callback) {
568 if (callback) {
563 callback(msg);
569 callback(msg);
564 }
570 }
565 };
571 };
566
572
567 // dispatch IOPub messages to respective handlers.
573 // dispatch IOPub messages to respective handlers.
568 // each message type should have a handler.
574 // each message type should have a handler.
569 Kernel.prototype._handle_iopub_message = function (e) {
575 Kernel.prototype._handle_iopub_message = function (e) {
570 var msg = $.parseJSON(e.data);
576 var msg = $.parseJSON(e.data);
571
577
572 var handler = this.get_iopub_handler(msg.header.msg_type);
578 var handler = this.get_iopub_handler(msg.header.msg_type);
573 if (handler !== undefined) {
579 if (handler !== undefined) {
574 handler(msg);
580 handler(msg);
575 }
581 }
576 };
582 };
577
583
578
584
579 Kernel.prototype._handle_input_request = function (e) {
585 Kernel.prototype._handle_input_request = function (e) {
580 var request = $.parseJSON(e.data);
586 var request = $.parseJSON(e.data);
581 var header = request.header;
587 var header = request.header;
582 var content = request.content;
588 var content = request.content;
583 var metadata = request.metadata;
589 var metadata = request.metadata;
584 var msg_type = header.msg_type;
590 var msg_type = header.msg_type;
585 if (msg_type !== 'input_request') {
591 if (msg_type !== 'input_request') {
586 console.log("Invalid input request!", request);
592 console.log("Invalid input request!", request);
587 return;
593 return;
588 }
594 }
589 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
595 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
590 if (callbacks) {
596 if (callbacks) {
591 if (callbacks.input) {
597 if (callbacks.input) {
592 callbacks.input(request);
598 callbacks.input(request);
593 }
599 }
594 }
600 }
595 };
601 };
596
602
597
603
598 IPython.Kernel = Kernel;
604 IPython.Kernel = Kernel;
599
605
600 return IPython;
606 return IPython;
601
607
602 }(IPython));
608 }(IPython));
603
609
@@ -1,118 +1,119 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var Session = function(notebook_name, notebook_path, notebook){
17 var Session = function(notebook, options){
18 this.kernel = null;
18 this.kernel = null;
19 this.id = null;
19 this.id = null;
20 this.name = notebook_name;
21 this.path = notebook_path;
22 this.notebook = notebook;
20 this.notebook = notebook;
23 this._baseProjectUrl = notebook.baseProjectUrl();
21 this.name = notebook.notebook_name;
22 this.path = notebook.notebook_path;
23 this.base_url = notebook.base_url;
24 this.base_kernel_url = options.base_kernel_url || utils.get_body_data("baseKernelUrl");
24 };
25 };
25
26
26 Session.prototype.start = function(callback) {
27 Session.prototype.start = function(callback) {
27 var that = this;
28 var that = this;
28 var model = {
29 var model = {
29 notebook : {
30 notebook : {
30 name : this.name,
31 name : this.name,
31 path : this.path
32 path : this.path
32 }
33 }
33 };
34 };
34 var settings = {
35 var settings = {
35 processData : false,
36 processData : false,
36 cache : false,
37 cache : false,
37 type : "POST",
38 type : "POST",
38 data: JSON.stringify(model),
39 data: JSON.stringify(model),
39 dataType : "json",
40 dataType : "json",
40 success : function (data, status, xhr) {
41 success : function (data, status, xhr) {
41 that._handle_start_success(data);
42 that._handle_start_success(data);
42 if (callback) {
43 if (callback) {
43 callback(data, status, xhr);
44 callback(data, status, xhr);
44 }
45 }
45 },
46 },
46 };
47 };
47 var url = utils.url_join_encode(this._baseProjectUrl, 'api/sessions');
48 var url = utils.url_join_encode(this.base_url, 'api/sessions');
48 $.ajax(url, settings);
49 $.ajax(url, settings);
49 };
50 };
50
51
51 Session.prototype.rename_notebook = function (name, path) {
52 Session.prototype.rename_notebook = function (name, path) {
52 this.name = name;
53 this.name = name;
53 this.path = path;
54 this.path = path;
54 var model = {
55 var model = {
55 notebook : {
56 notebook : {
56 name : this.name,
57 name : this.name,
57 path : this.path
58 path : this.path
58 }
59 }
59 };
60 };
60 var settings = {
61 var settings = {
61 processData : false,
62 processData : false,
62 cache : false,
63 cache : false,
63 type : "PATCH",
64 type : "PATCH",
64 data: JSON.stringify(model),
65 data: JSON.stringify(model),
65 dataType : "json",
66 dataType : "json",
66 };
67 };
67 var url = utils.url_join_encode(this._baseProjectUrl, 'api/sessions', this.id);
68 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
68 $.ajax(url, settings);
69 $.ajax(url, settings);
69 };
70 };
70
71
71 Session.prototype.delete = function() {
72 Session.prototype.delete = function() {
72 var settings = {
73 var settings = {
73 processData : false,
74 processData : false,
74 cache : false,
75 cache : false,
75 type : "DELETE",
76 type : "DELETE",
76 dataType : "json",
77 dataType : "json",
77 };
78 };
78 this.kernel.running = false;
79 this.kernel.running = false;
79 var url = utils.url_join_encode(this._baseProjectUrl, 'api/sessions', this.id);
80 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
80 $.ajax(url, settings);
81 $.ajax(url, settings);
81 };
82 };
82
83
83 // Kernel related things
84 // Kernel related things
84 /**
85 /**
85 * Create the Kernel object associated with this Session.
86 * Create the Kernel object associated with this Session.
86 *
87 *
87 * @method _handle_start_success
88 * @method _handle_start_success
88 */
89 */
89 Session.prototype._handle_start_success = function (data, status, xhr) {
90 Session.prototype._handle_start_success = function (data, status, xhr) {
90 this.id = data.id;
91 this.id = data.id;
91 var base_url = utils.url_path_join($('body').data('baseKernelUrl'), "api/kernels");
92 var kernel_service_url = utils.url_path_join(this.base_kernel_url, "api/kernels");
92 this.kernel = new IPython.Kernel(base_url);
93 this.kernel = new IPython.Kernel(kernel_service_url);
93 this.kernel._kernel_started(data.kernel);
94 this.kernel._kernel_started(data.kernel);
94 };
95 };
95
96
96 /**
97 /**
97 * Prompt the user to restart the IPython kernel.
98 * Prompt the user to restart the IPython kernel.
98 *
99 *
99 * @method restart_kernel
100 * @method restart_kernel
100 */
101 */
101 Session.prototype.restart_kernel = function () {
102 Session.prototype.restart_kernel = function () {
102 this.kernel.restart();
103 this.kernel.restart();
103 };
104 };
104
105
105 Session.prototype.interrupt_kernel = function() {
106 Session.prototype.interrupt_kernel = function() {
106 this.kernel.interrupt();
107 this.kernel.interrupt();
107 };
108 };
108
109
109
110
110 Session.prototype.kill_kernel = function() {
111 Session.prototype.kill_kernel = function() {
111 this.kernel.kill();
112 this.kernel.kill();
112 };
113 };
113
114
114 IPython.Session = Session;
115 IPython.Session = Session;
115
116
116 return IPython;
117 return IPython;
117
118
118 }(IPython));
119 }(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: file renamed from IPython/html/tests/casperjs/README.md to IPython/html/tests/README.md
NO CONTENT: file renamed from IPython/html/tests/casperjs/README.md to IPython/html/tests/README.md
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/misc_tests.js to IPython/html/tests/base/misc.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/misc_tests.js to IPython/html/tests/base/misc.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/nb_arrow_keys.js to IPython/html/tests/notebook/arrow_keys.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/nb_arrow_keys.js to IPython/html/tests/notebook/arrow_keys.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/display_image.js to IPython/html/tests/notebook/display_image.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/display_image.js to IPython/html/tests/notebook/display_image.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/empty_nb_arrow_keys.js to IPython/html/tests/notebook/empty_arrow_keys.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/empty_nb_arrow_keys.js to IPython/html/tests/notebook/empty_arrow_keys.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/execute_code_cell.js to IPython/html/tests/notebook/execute_code.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/execute_code_cell.js to IPython/html/tests/notebook/execute_code.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/inject_js.js to IPython/html/tests/notebook/inject_js.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/inject_js.js to IPython/html/tests/notebook/inject_js.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/check_interrupt.js to IPython/html/tests/notebook/interrupt.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/check_interrupt.js to IPython/html/tests/notebook/interrupt.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/isolated_svg.js to IPython/html/tests/notebook/isolated_svg.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/isolated_svg.js to IPython/html/tests/notebook/isolated_svg.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/render_markdown.js to IPython/html/tests/notebook/markdown.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/render_markdown.js to IPython/html/tests/notebook/markdown.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/merge_cells.js to IPython/html/tests/notebook/merge_cells.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/merge_cells.js to IPython/html/tests/notebook/merge_cells.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/nb_roundtrip.js to IPython/html/tests/notebook/roundtrip.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/nb_roundtrip.js to IPython/html/tests/notebook/roundtrip.js
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/safe_append_output.js to IPython/html/tests/notebook/safe_append_output.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/safe_append_output.js to IPython/html/tests/notebook/safe_append_output.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/save_notebook.js to IPython/html/tests/notebook/save.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/save_notebook.js to IPython/html/tests/notebook/save.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/shutdown_notebook.js to IPython/html/tests/notebook/shutdown.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/shutdown_notebook.js to IPython/html/tests/notebook/shutdown.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/tooltip.js to IPython/html/tests/notebook/tooltip.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/tooltip.js to IPython/html/tests/notebook/tooltip.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/kernel_test.js to IPython/html/tests/services/kernel.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/kernel_test.js to IPython/html/tests/services/kernel.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/dashboard_nav.js to IPython/html/tests/tree/dashboard_nav.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/dashboard_nav.js to IPython/html/tests/tree/dashboard_nav.js
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/util.js to IPython/html/tests/util.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/util.js to IPython/html/tests/util.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets.js to IPython/html/tests/widgets/widget.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets.js to IPython/html/tests/widgets/widget.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_bool.js to IPython/html/tests/widgets/widget_bool.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_bool.js to IPython/html/tests/widgets/widget_bool.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_button.js to IPython/html/tests/widgets/widget_button.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_button.js to IPython/html/tests/widgets/widget_button.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_container.js to IPython/html/tests/widgets/widget_container.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_container.js to IPython/html/tests/widgets/widget_container.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_float.js to IPython/html/tests/widgets/widget_float.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_float.js to IPython/html/tests/widgets/widget_float.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_image.js to IPython/html/tests/widgets/widget_image.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_image.js to IPython/html/tests/widgets/widget_image.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_int.js to IPython/html/tests/widgets/widget_int.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_int.js to IPython/html/tests/widgets/widget_int.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_multicontainer.js to IPython/html/tests/widgets/widget_multicontainer.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_multicontainer.js to IPython/html/tests/widgets/widget_multicontainer.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_selection.js to IPython/html/tests/widgets/widget_selection.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_selection.js to IPython/html/tests/widgets/widget_selection.js
1 NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_string.js to IPython/html/tests/widgets/widget_string.js
NO CONTENT: file renamed from IPython/html/tests/casperjs/test_cases/widgets_string.js to IPython/html/tests/widgets/widget_string.js
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: 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/parallel/parallel_pylab.ipy to examples/parallel/plotting/parallel_plot.ipy
NO CONTENT: file renamed from examples/parallel/parallel_pylab.ipy to examples/parallel/plotting/parallel_plot.ipy
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: 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/tests/pylab_figshow.py to examples/tests/inline_figshow.py
NO CONTENT: file renamed from examples/tests/pylab_figshow.py to examples/tests/inline_figshow.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: 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
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