##// END OF EJS Templates
Removed ability to clear stdout and stderr individually.
Jonathan Frederic -
Show More
@@ -1,691 +1,671 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
26
27 from .displaypub import publish_display_data
27 from .displaypub import publish_display_data
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # utility functions
30 # utility functions
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33 def _safe_exists(path):
33 def _safe_exists(path):
34 """Check path, but don't let exceptions raise"""
34 """Check path, but don't let exceptions raise"""
35 try:
35 try:
36 return os.path.exists(path)
36 return os.path.exists(path)
37 except Exception:
37 except Exception:
38 return False
38 return False
39
39
40 def _merge(d1, d2):
40 def _merge(d1, d2):
41 """Like update, but merges sub-dicts instead of clobbering at the top level.
41 """Like update, but merges sub-dicts instead of clobbering at the top level.
42
42
43 Updates d1 in-place
43 Updates d1 in-place
44 """
44 """
45
45
46 if not isinstance(d2, dict) or not isinstance(d1, dict):
46 if not isinstance(d2, dict) or not isinstance(d1, dict):
47 return d2
47 return d2
48 for key, value in d2.items():
48 for key, value in d2.items():
49 d1[key] = _merge(d1.get(key), value)
49 d1[key] = _merge(d1.get(key), value)
50 return d1
50 return d1
51
51
52 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
52 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
53 """internal implementation of all display_foo methods
53 """internal implementation of all display_foo methods
54
54
55 Parameters
55 Parameters
56 ----------
56 ----------
57 mimetype : str
57 mimetype : str
58 The mimetype to be published (e.g. 'image/png')
58 The mimetype to be published (e.g. 'image/png')
59 objs : tuple of objects
59 objs : tuple of objects
60 The Python objects to display, or if raw=True raw text data to
60 The Python objects to display, or if raw=True raw text data to
61 display.
61 display.
62 raw : bool
62 raw : bool
63 Are the data objects raw data or Python objects that need to be
63 Are the data objects raw data or Python objects that need to be
64 formatted before display? [default: False]
64 formatted before display? [default: False]
65 metadata : dict (optional)
65 metadata : dict (optional)
66 Metadata to be associated with the specific mimetype output.
66 Metadata to be associated with the specific mimetype output.
67 """
67 """
68 if metadata:
68 if metadata:
69 metadata = {mimetype: metadata}
69 metadata = {mimetype: metadata}
70 if raw:
70 if raw:
71 # turn list of pngdata into list of { 'image/png': pngdata }
71 # turn list of pngdata into list of { 'image/png': pngdata }
72 objs = [ {mimetype: obj} for obj in objs ]
72 objs = [ {mimetype: obj} for obj in objs ]
73 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
73 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
74
74
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76 # Main functions
76 # Main functions
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78
78
79 def display(*objs, **kwargs):
79 def display(*objs, **kwargs):
80 """Display a Python object in all frontends.
80 """Display a Python object in all frontends.
81
81
82 By default all representations will be computed and sent to the frontends.
82 By default all representations will be computed and sent to the frontends.
83 Frontends can decide which representation is used and how.
83 Frontends can decide which representation is used and how.
84
84
85 Parameters
85 Parameters
86 ----------
86 ----------
87 objs : tuple of objects
87 objs : tuple of objects
88 The Python objects to display.
88 The Python objects to display.
89 raw : bool, optional
89 raw : bool, optional
90 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
90 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
91 or Python objects that need to be formatted before display? [default: False]
91 or Python objects that need to be formatted before display? [default: False]
92 include : list or tuple, optional
92 include : list or tuple, optional
93 A list of format type strings (MIME types) to include in the
93 A list of format type strings (MIME types) to include in the
94 format data dict. If this is set *only* the format types included
94 format data dict. If this is set *only* the format types included
95 in this list will be computed.
95 in this list will be computed.
96 exclude : list or tuple, optional
96 exclude : list or tuple, optional
97 A list of format type strings (MIME types) to exclude in the format
97 A list of format type strings (MIME types) to exclude in the format
98 data dict. If this is set all format types will be computed,
98 data dict. If this is set all format types will be computed,
99 except for those included in this argument.
99 except for those included in this argument.
100 metadata : dict, optional
100 metadata : dict, optional
101 A dictionary of metadata to associate with the output.
101 A dictionary of metadata to associate with the output.
102 mime-type keys in this dictionary will be associated with the individual
102 mime-type keys in this dictionary will be associated with the individual
103 representation formats, if they exist.
103 representation formats, if they exist.
104 """
104 """
105 raw = kwargs.get('raw', False)
105 raw = kwargs.get('raw', False)
106 include = kwargs.get('include')
106 include = kwargs.get('include')
107 exclude = kwargs.get('exclude')
107 exclude = kwargs.get('exclude')
108 metadata = kwargs.get('metadata')
108 metadata = kwargs.get('metadata')
109
109
110 from IPython.core.interactiveshell import InteractiveShell
110 from IPython.core.interactiveshell import InteractiveShell
111
111
112 if raw:
112 if raw:
113 for obj in objs:
113 for obj in objs:
114 publish_display_data('display', obj, metadata)
114 publish_display_data('display', obj, metadata)
115 else:
115 else:
116 format = InteractiveShell.instance().display_formatter.format
116 format = InteractiveShell.instance().display_formatter.format
117 for obj in objs:
117 for obj in objs:
118 format_dict, md_dict = format(obj, include=include, exclude=exclude)
118 format_dict, md_dict = format(obj, include=include, exclude=exclude)
119 if metadata:
119 if metadata:
120 # kwarg-specified metadata gets precedence
120 # kwarg-specified metadata gets precedence
121 _merge(md_dict, metadata)
121 _merge(md_dict, metadata)
122 publish_display_data('display', format_dict, md_dict)
122 publish_display_data('display', format_dict, md_dict)
123
123
124
124
125 def display_pretty(*objs, **kwargs):
125 def display_pretty(*objs, **kwargs):
126 """Display the pretty (default) representation of an object.
126 """Display the pretty (default) representation of an object.
127
127
128 Parameters
128 Parameters
129 ----------
129 ----------
130 objs : tuple of objects
130 objs : tuple of objects
131 The Python objects to display, or if raw=True raw text data to
131 The Python objects to display, or if raw=True raw text data to
132 display.
132 display.
133 raw : bool
133 raw : bool
134 Are the data objects raw data or Python objects that need to be
134 Are the data objects raw data or Python objects that need to be
135 formatted before display? [default: False]
135 formatted before display? [default: False]
136 metadata : dict (optional)
136 metadata : dict (optional)
137 Metadata to be associated with the specific mimetype output.
137 Metadata to be associated with the specific mimetype output.
138 """
138 """
139 _display_mimetype('text/plain', objs, **kwargs)
139 _display_mimetype('text/plain', objs, **kwargs)
140
140
141
141
142 def display_html(*objs, **kwargs):
142 def display_html(*objs, **kwargs):
143 """Display the HTML representation of an object.
143 """Display the HTML representation of an object.
144
144
145 Parameters
145 Parameters
146 ----------
146 ----------
147 objs : tuple of objects
147 objs : tuple of objects
148 The Python objects to display, or if raw=True raw HTML data to
148 The Python objects to display, or if raw=True raw HTML data to
149 display.
149 display.
150 raw : bool
150 raw : bool
151 Are the data objects raw data or Python objects that need to be
151 Are the data objects raw data or Python objects that need to be
152 formatted before display? [default: False]
152 formatted before display? [default: False]
153 metadata : dict (optional)
153 metadata : dict (optional)
154 Metadata to be associated with the specific mimetype output.
154 Metadata to be associated with the specific mimetype output.
155 """
155 """
156 _display_mimetype('text/html', objs, **kwargs)
156 _display_mimetype('text/html', objs, **kwargs)
157
157
158
158
159 def display_svg(*objs, **kwargs):
159 def display_svg(*objs, **kwargs):
160 """Display the SVG representation of an object.
160 """Display the SVG representation of an object.
161
161
162 Parameters
162 Parameters
163 ----------
163 ----------
164 objs : tuple of objects
164 objs : tuple of objects
165 The Python objects to display, or if raw=True raw svg data to
165 The Python objects to display, or if raw=True raw svg data to
166 display.
166 display.
167 raw : bool
167 raw : bool
168 Are the data objects raw data or Python objects that need to be
168 Are the data objects raw data or Python objects that need to be
169 formatted before display? [default: False]
169 formatted before display? [default: False]
170 metadata : dict (optional)
170 metadata : dict (optional)
171 Metadata to be associated with the specific mimetype output.
171 Metadata to be associated with the specific mimetype output.
172 """
172 """
173 _display_mimetype('image/svg+xml', objs, **kwargs)
173 _display_mimetype('image/svg+xml', objs, **kwargs)
174
174
175
175
176 def display_png(*objs, **kwargs):
176 def display_png(*objs, **kwargs):
177 """Display the PNG representation of an object.
177 """Display the PNG representation of an object.
178
178
179 Parameters
179 Parameters
180 ----------
180 ----------
181 objs : tuple of objects
181 objs : tuple of objects
182 The Python objects to display, or if raw=True raw png data to
182 The Python objects to display, or if raw=True raw png data to
183 display.
183 display.
184 raw : bool
184 raw : bool
185 Are the data objects raw data or Python objects that need to be
185 Are the data objects raw data or Python objects that need to be
186 formatted before display? [default: False]
186 formatted before display? [default: False]
187 metadata : dict (optional)
187 metadata : dict (optional)
188 Metadata to be associated with the specific mimetype output.
188 Metadata to be associated with the specific mimetype output.
189 """
189 """
190 _display_mimetype('image/png', objs, **kwargs)
190 _display_mimetype('image/png', objs, **kwargs)
191
191
192
192
193 def display_jpeg(*objs, **kwargs):
193 def display_jpeg(*objs, **kwargs):
194 """Display the JPEG representation of an object.
194 """Display the JPEG representation of an object.
195
195
196 Parameters
196 Parameters
197 ----------
197 ----------
198 objs : tuple of objects
198 objs : tuple of objects
199 The Python objects to display, or if raw=True raw JPEG data to
199 The Python objects to display, or if raw=True raw JPEG data to
200 display.
200 display.
201 raw : bool
201 raw : bool
202 Are the data objects raw data or Python objects that need to be
202 Are the data objects raw data or Python objects that need to be
203 formatted before display? [default: False]
203 formatted before display? [default: False]
204 metadata : dict (optional)
204 metadata : dict (optional)
205 Metadata to be associated with the specific mimetype output.
205 Metadata to be associated with the specific mimetype output.
206 """
206 """
207 _display_mimetype('image/jpeg', objs, **kwargs)
207 _display_mimetype('image/jpeg', objs, **kwargs)
208
208
209
209
210 def display_latex(*objs, **kwargs):
210 def display_latex(*objs, **kwargs):
211 """Display the LaTeX representation of an object.
211 """Display the LaTeX representation of an object.
212
212
213 Parameters
213 Parameters
214 ----------
214 ----------
215 objs : tuple of objects
215 objs : tuple of objects
216 The Python objects to display, or if raw=True raw latex data to
216 The Python objects to display, or if raw=True raw latex data to
217 display.
217 display.
218 raw : bool
218 raw : bool
219 Are the data objects raw data or Python objects that need to be
219 Are the data objects raw data or Python objects that need to be
220 formatted before display? [default: False]
220 formatted before display? [default: False]
221 metadata : dict (optional)
221 metadata : dict (optional)
222 Metadata to be associated with the specific mimetype output.
222 Metadata to be associated with the specific mimetype output.
223 """
223 """
224 _display_mimetype('text/latex', objs, **kwargs)
224 _display_mimetype('text/latex', objs, **kwargs)
225
225
226
226
227 def display_json(*objs, **kwargs):
227 def display_json(*objs, **kwargs):
228 """Display the JSON representation of an object.
228 """Display the JSON representation of an object.
229
229
230 Note that not many frontends support displaying JSON.
230 Note that not many frontends support displaying JSON.
231
231
232 Parameters
232 Parameters
233 ----------
233 ----------
234 objs : tuple of objects
234 objs : tuple of objects
235 The Python objects to display, or if raw=True raw json data to
235 The Python objects to display, or if raw=True raw json data to
236 display.
236 display.
237 raw : bool
237 raw : bool
238 Are the data objects raw data or Python objects that need to be
238 Are the data objects raw data or Python objects that need to be
239 formatted before display? [default: False]
239 formatted before display? [default: False]
240 metadata : dict (optional)
240 metadata : dict (optional)
241 Metadata to be associated with the specific mimetype output.
241 Metadata to be associated with the specific mimetype output.
242 """
242 """
243 _display_mimetype('application/json', objs, **kwargs)
243 _display_mimetype('application/json', objs, **kwargs)
244
244
245
245
246 def display_javascript(*objs, **kwargs):
246 def display_javascript(*objs, **kwargs):
247 """Display the Javascript representation of an object.
247 """Display the Javascript representation of an object.
248
248
249 Parameters
249 Parameters
250 ----------
250 ----------
251 objs : tuple of objects
251 objs : tuple of objects
252 The Python objects to display, or if raw=True raw javascript data to
252 The Python objects to display, or if raw=True raw javascript data to
253 display.
253 display.
254 raw : bool
254 raw : bool
255 Are the data objects raw data or Python objects that need to be
255 Are the data objects raw data or Python objects that need to be
256 formatted before display? [default: False]
256 formatted before display? [default: False]
257 metadata : dict (optional)
257 metadata : dict (optional)
258 Metadata to be associated with the specific mimetype output.
258 Metadata to be associated with the specific mimetype output.
259 """
259 """
260 _display_mimetype('application/javascript', objs, **kwargs)
260 _display_mimetype('application/javascript', objs, **kwargs)
261
261
262 #-----------------------------------------------------------------------------
262 #-----------------------------------------------------------------------------
263 # Smart classes
263 # Smart classes
264 #-----------------------------------------------------------------------------
264 #-----------------------------------------------------------------------------
265
265
266
266
267 class DisplayObject(object):
267 class DisplayObject(object):
268 """An object that wraps data to be displayed."""
268 """An object that wraps data to be displayed."""
269
269
270 _read_flags = 'r'
270 _read_flags = 'r'
271
271
272 def __init__(self, data=None, url=None, filename=None):
272 def __init__(self, data=None, url=None, filename=None):
273 """Create a display object given raw data.
273 """Create a display object given raw data.
274
274
275 When this object is returned by an expression or passed to the
275 When this object is returned by an expression or passed to the
276 display function, it will result in the data being displayed
276 display function, it will result in the data being displayed
277 in the frontend. The MIME type of the data should match the
277 in the frontend. The MIME type of the data should match the
278 subclasses used, so the Png subclass should be used for 'image/png'
278 subclasses used, so the Png subclass should be used for 'image/png'
279 data. If the data is a URL, the data will first be downloaded
279 data. If the data is a URL, the data will first be downloaded
280 and then displayed. If
280 and then displayed. If
281
281
282 Parameters
282 Parameters
283 ----------
283 ----------
284 data : unicode, str or bytes
284 data : unicode, str or bytes
285 The raw data or a URL or file to load the data from
285 The raw data or a URL or file to load the data from
286 url : unicode
286 url : unicode
287 A URL to download the data from.
287 A URL to download the data from.
288 filename : unicode
288 filename : unicode
289 Path to a local file to load the data from.
289 Path to a local file to load the data from.
290 """
290 """
291 if data is not None and isinstance(data, string_types):
291 if data is not None and isinstance(data, string_types):
292 if data.startswith('http') and url is None:
292 if data.startswith('http') and url is None:
293 url = data
293 url = data
294 filename = None
294 filename = None
295 data = None
295 data = None
296 elif _safe_exists(data) and filename is None:
296 elif _safe_exists(data) and filename is None:
297 url = None
297 url = None
298 filename = data
298 filename = data
299 data = None
299 data = None
300
300
301 self.data = data
301 self.data = data
302 self.url = url
302 self.url = url
303 self.filename = None if filename is None else unicode(filename)
303 self.filename = None if filename is None else unicode(filename)
304
304
305 self.reload()
305 self.reload()
306
306
307 def reload(self):
307 def reload(self):
308 """Reload the raw data from file or URL."""
308 """Reload the raw data from file or URL."""
309 if self.filename is not None:
309 if self.filename is not None:
310 with open(self.filename, self._read_flags) as f:
310 with open(self.filename, self._read_flags) as f:
311 self.data = f.read()
311 self.data = f.read()
312 elif self.url is not None:
312 elif self.url is not None:
313 try:
313 try:
314 import urllib2
314 import urllib2
315 response = urllib2.urlopen(self.url)
315 response = urllib2.urlopen(self.url)
316 self.data = response.read()
316 self.data = response.read()
317 # extract encoding from header, if there is one:
317 # extract encoding from header, if there is one:
318 encoding = None
318 encoding = None
319 for sub in response.headers['content-type'].split(';'):
319 for sub in response.headers['content-type'].split(';'):
320 sub = sub.strip()
320 sub = sub.strip()
321 if sub.startswith('charset'):
321 if sub.startswith('charset'):
322 encoding = sub.split('=')[-1].strip()
322 encoding = sub.split('=')[-1].strip()
323 break
323 break
324 # decode data, if an encoding was specified
324 # decode data, if an encoding was specified
325 if encoding:
325 if encoding:
326 self.data = self.data.decode(encoding, 'replace')
326 self.data = self.data.decode(encoding, 'replace')
327 except:
327 except:
328 self.data = None
328 self.data = None
329
329
330 class Pretty(DisplayObject):
330 class Pretty(DisplayObject):
331
331
332 def _repr_pretty_(self):
332 def _repr_pretty_(self):
333 return self.data
333 return self.data
334
334
335
335
336 class HTML(DisplayObject):
336 class HTML(DisplayObject):
337
337
338 def _repr_html_(self):
338 def _repr_html_(self):
339 return self.data
339 return self.data
340
340
341 def __html__(self):
341 def __html__(self):
342 """
342 """
343 This method exists to inform other HTML-using modules (e.g. Markupsafe,
343 This method exists to inform other HTML-using modules (e.g. Markupsafe,
344 htmltag, etc) that this object is HTML and does not need things like
344 htmltag, etc) that this object is HTML and does not need things like
345 special characters (<>&) escaped.
345 special characters (<>&) escaped.
346 """
346 """
347 return self._repr_html_()
347 return self._repr_html_()
348
348
349
349
350 class Math(DisplayObject):
350 class Math(DisplayObject):
351
351
352 def _repr_latex_(self):
352 def _repr_latex_(self):
353 s = self.data.strip('$')
353 s = self.data.strip('$')
354 return "$$%s$$" % s
354 return "$$%s$$" % s
355
355
356
356
357 class Latex(DisplayObject):
357 class Latex(DisplayObject):
358
358
359 def _repr_latex_(self):
359 def _repr_latex_(self):
360 return self.data
360 return self.data
361
361
362
362
363 class SVG(DisplayObject):
363 class SVG(DisplayObject):
364
364
365 # wrap data in a property, which extracts the <svg> tag, discarding
365 # wrap data in a property, which extracts the <svg> tag, discarding
366 # document headers
366 # document headers
367 _data = None
367 _data = None
368
368
369 @property
369 @property
370 def data(self):
370 def data(self):
371 return self._data
371 return self._data
372
372
373 @data.setter
373 @data.setter
374 def data(self, svg):
374 def data(self, svg):
375 if svg is None:
375 if svg is None:
376 self._data = None
376 self._data = None
377 return
377 return
378 # parse into dom object
378 # parse into dom object
379 from xml.dom import minidom
379 from xml.dom import minidom
380 svg = cast_bytes_py2(svg)
380 svg = cast_bytes_py2(svg)
381 x = minidom.parseString(svg)
381 x = minidom.parseString(svg)
382 # get svg tag (should be 1)
382 # get svg tag (should be 1)
383 found_svg = x.getElementsByTagName('svg')
383 found_svg = x.getElementsByTagName('svg')
384 if found_svg:
384 if found_svg:
385 svg = found_svg[0].toxml()
385 svg = found_svg[0].toxml()
386 else:
386 else:
387 # fallback on the input, trust the user
387 # fallback on the input, trust the user
388 # but this is probably an error.
388 # but this is probably an error.
389 pass
389 pass
390 svg = cast_unicode(svg)
390 svg = cast_unicode(svg)
391 self._data = svg
391 self._data = svg
392
392
393 def _repr_svg_(self):
393 def _repr_svg_(self):
394 return self.data
394 return self.data
395
395
396
396
397 class JSON(DisplayObject):
397 class JSON(DisplayObject):
398
398
399 def _repr_json_(self):
399 def _repr_json_(self):
400 return self.data
400 return self.data
401
401
402 css_t = """$("head").append($("<link/>").attr({
402 css_t = """$("head").append($("<link/>").attr({
403 rel: "stylesheet",
403 rel: "stylesheet",
404 type: "text/css",
404 type: "text/css",
405 href: "%s"
405 href: "%s"
406 }));
406 }));
407 """
407 """
408
408
409 lib_t1 = """$.getScript("%s", function () {
409 lib_t1 = """$.getScript("%s", function () {
410 """
410 """
411 lib_t2 = """});
411 lib_t2 = """});
412 """
412 """
413
413
414 class Javascript(DisplayObject):
414 class Javascript(DisplayObject):
415
415
416 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
416 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
417 """Create a Javascript display object given raw data.
417 """Create a Javascript display object given raw data.
418
418
419 When this object is returned by an expression or passed to the
419 When this object is returned by an expression or passed to the
420 display function, it will result in the data being displayed
420 display function, it will result in the data being displayed
421 in the frontend. If the data is a URL, the data will first be
421 in the frontend. If the data is a URL, the data will first be
422 downloaded and then displayed.
422 downloaded and then displayed.
423
423
424 In the Notebook, the containing element will be available as `element`,
424 In the Notebook, the containing element will be available as `element`,
425 and jQuery will be available. The output area starts hidden, so if
425 and jQuery will be available. The output area starts hidden, so if
426 the js appends content to `element` that should be visible, then
426 the js appends content to `element` that should be visible, then
427 it must call `container.show()` to unhide the area.
427 it must call `container.show()` to unhide the area.
428
428
429 Parameters
429 Parameters
430 ----------
430 ----------
431 data : unicode, str or bytes
431 data : unicode, str or bytes
432 The Javascript source code or a URL to download it from.
432 The Javascript source code or a URL to download it from.
433 url : unicode
433 url : unicode
434 A URL to download the data from.
434 A URL to download the data from.
435 filename : unicode
435 filename : unicode
436 Path to a local file to load the data from.
436 Path to a local file to load the data from.
437 lib : list or str
437 lib : list or str
438 A sequence of Javascript library URLs to load asynchronously before
438 A sequence of Javascript library URLs to load asynchronously before
439 running the source code. The full URLs of the libraries should
439 running the source code. The full URLs of the libraries should
440 be given. A single Javascript library URL can also be given as a
440 be given. A single Javascript library URL can also be given as a
441 string.
441 string.
442 css: : list or str
442 css: : list or str
443 A sequence of css files to load before running the source code.
443 A sequence of css files to load before running the source code.
444 The full URLs of the css files should be given. A single css URL
444 The full URLs of the css files should be given. A single css URL
445 can also be given as a string.
445 can also be given as a string.
446 """
446 """
447 if isinstance(lib, basestring):
447 if isinstance(lib, basestring):
448 lib = [lib]
448 lib = [lib]
449 elif lib is None:
449 elif lib is None:
450 lib = []
450 lib = []
451 if isinstance(css, basestring):
451 if isinstance(css, basestring):
452 css = [css]
452 css = [css]
453 elif css is None:
453 elif css is None:
454 css = []
454 css = []
455 if not isinstance(lib, (list,tuple)):
455 if not isinstance(lib, (list,tuple)):
456 raise TypeError('expected sequence, got: %r' % lib)
456 raise TypeError('expected sequence, got: %r' % lib)
457 if not isinstance(css, (list,tuple)):
457 if not isinstance(css, (list,tuple)):
458 raise TypeError('expected sequence, got: %r' % css)
458 raise TypeError('expected sequence, got: %r' % css)
459 self.lib = lib
459 self.lib = lib
460 self.css = css
460 self.css = css
461 super(Javascript, self).__init__(data=data, url=url, filename=filename)
461 super(Javascript, self).__init__(data=data, url=url, filename=filename)
462
462
463 def _repr_javascript_(self):
463 def _repr_javascript_(self):
464 r = ''
464 r = ''
465 for c in self.css:
465 for c in self.css:
466 r += css_t % c
466 r += css_t % c
467 for l in self.lib:
467 for l in self.lib:
468 r += lib_t1 % l
468 r += lib_t1 % l
469 r += self.data
469 r += self.data
470 r += lib_t2*len(self.lib)
470 r += lib_t2*len(self.lib)
471 return r
471 return r
472
472
473 # constants for identifying png/jpeg data
473 # constants for identifying png/jpeg data
474 _PNG = b'\x89PNG\r\n\x1a\n'
474 _PNG = b'\x89PNG\r\n\x1a\n'
475 _JPEG = b'\xff\xd8'
475 _JPEG = b'\xff\xd8'
476
476
477 def _pngxy(data):
477 def _pngxy(data):
478 """read the (width, height) from a PNG header"""
478 """read the (width, height) from a PNG header"""
479 ihdr = data.index(b'IHDR')
479 ihdr = data.index(b'IHDR')
480 # next 8 bytes are width/height
480 # next 8 bytes are width/height
481 w4h4 = data[ihdr+4:ihdr+12]
481 w4h4 = data[ihdr+4:ihdr+12]
482 return struct.unpack('>ii', w4h4)
482 return struct.unpack('>ii', w4h4)
483
483
484 def _jpegxy(data):
484 def _jpegxy(data):
485 """read the (width, height) from a JPEG header"""
485 """read the (width, height) from a JPEG header"""
486 # adapted from http://www.64lines.com/jpeg-width-height
486 # adapted from http://www.64lines.com/jpeg-width-height
487
487
488 idx = 4
488 idx = 4
489 while True:
489 while True:
490 block_size = struct.unpack('>H', data[idx:idx+2])[0]
490 block_size = struct.unpack('>H', data[idx:idx+2])[0]
491 idx = idx + block_size
491 idx = idx + block_size
492 if data[idx:idx+2] == b'\xFF\xC0':
492 if data[idx:idx+2] == b'\xFF\xC0':
493 # found Start of Frame
493 # found Start of Frame
494 iSOF = idx
494 iSOF = idx
495 break
495 break
496 else:
496 else:
497 # read another block
497 # read another block
498 idx += 2
498 idx += 2
499
499
500 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
500 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
501 return w, h
501 return w, h
502
502
503 class Image(DisplayObject):
503 class Image(DisplayObject):
504
504
505 _read_flags = 'rb'
505 _read_flags = 'rb'
506 _FMT_JPEG = u'jpeg'
506 _FMT_JPEG = u'jpeg'
507 _FMT_PNG = u'png'
507 _FMT_PNG = u'png'
508 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
508 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
509
509
510 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
510 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
511 """Create a PNG/JPEG image object given raw data.
511 """Create a PNG/JPEG image object given raw data.
512
512
513 When this object is returned by an input cell or passed to the
513 When this object is returned by an input cell or passed to the
514 display function, it will result in the image being displayed
514 display function, it will result in the image being displayed
515 in the frontend.
515 in the frontend.
516
516
517 Parameters
517 Parameters
518 ----------
518 ----------
519 data : unicode, str or bytes
519 data : unicode, str or bytes
520 The raw image data or a URL or filename to load the data from.
520 The raw image data or a URL or filename to load the data from.
521 This always results in embedded image data.
521 This always results in embedded image data.
522 url : unicode
522 url : unicode
523 A URL to download the data from. If you specify `url=`,
523 A URL to download the data from. If you specify `url=`,
524 the image data will not be embedded unless you also specify `embed=True`.
524 the image data will not be embedded unless you also specify `embed=True`.
525 filename : unicode
525 filename : unicode
526 Path to a local file to load the data from.
526 Path to a local file to load the data from.
527 Images from a file are always embedded.
527 Images from a file are always embedded.
528 format : unicode
528 format : unicode
529 The format of the image data (png/jpeg/jpg). If a filename or URL is given
529 The format of the image data (png/jpeg/jpg). If a filename or URL is given
530 for format will be inferred from the filename extension.
530 for format will be inferred from the filename extension.
531 embed : bool
531 embed : bool
532 Should the image data be embedded using a data URI (True) or be
532 Should the image data be embedded using a data URI (True) or be
533 loaded using an <img> tag. Set this to True if you want the image
533 loaded using an <img> tag. Set this to True if you want the image
534 to be viewable later with no internet connection in the notebook.
534 to be viewable later with no internet connection in the notebook.
535
535
536 Default is `True`, unless the keyword argument `url` is set, then
536 Default is `True`, unless the keyword argument `url` is set, then
537 default value is `False`.
537 default value is `False`.
538
538
539 Note that QtConsole is not able to display images if `embed` is set to `False`
539 Note that QtConsole is not able to display images if `embed` is set to `False`
540 width : int
540 width : int
541 Width to which to constrain the image in html
541 Width to which to constrain the image in html
542 height : int
542 height : int
543 Height to which to constrain the image in html
543 Height to which to constrain the image in html
544 retina : bool
544 retina : bool
545 Automatically set the width and height to half of the measured
545 Automatically set the width and height to half of the measured
546 width and height.
546 width and height.
547 This only works for embedded images because it reads the width/height
547 This only works for embedded images because it reads the width/height
548 from image data.
548 from image data.
549 For non-embedded images, you can just set the desired display width
549 For non-embedded images, you can just set the desired display width
550 and height directly.
550 and height directly.
551
551
552 Examples
552 Examples
553 --------
553 --------
554 # embedded image data, works in qtconsole and notebook
554 # embedded image data, works in qtconsole and notebook
555 # when passed positionally, the first arg can be any of raw image data,
555 # when passed positionally, the first arg can be any of raw image data,
556 # a URL, or a filename from which to load image data.
556 # a URL, or a filename from which to load image data.
557 # The result is always embedding image data for inline images.
557 # The result is always embedding image data for inline images.
558 Image('http://www.google.fr/images/srpr/logo3w.png')
558 Image('http://www.google.fr/images/srpr/logo3w.png')
559 Image('/path/to/image.jpg')
559 Image('/path/to/image.jpg')
560 Image(b'RAW_PNG_DATA...')
560 Image(b'RAW_PNG_DATA...')
561
561
562 # Specifying Image(url=...) does not embed the image data,
562 # Specifying Image(url=...) does not embed the image data,
563 # it only generates `<img>` tag with a link to the source.
563 # it only generates `<img>` tag with a link to the source.
564 # This will not work in the qtconsole or offline.
564 # This will not work in the qtconsole or offline.
565 Image(url='http://www.google.fr/images/srpr/logo3w.png')
565 Image(url='http://www.google.fr/images/srpr/logo3w.png')
566
566
567 """
567 """
568 if filename is not None:
568 if filename is not None:
569 ext = self._find_ext(filename)
569 ext = self._find_ext(filename)
570 elif url is not None:
570 elif url is not None:
571 ext = self._find_ext(url)
571 ext = self._find_ext(url)
572 elif data is None:
572 elif data is None:
573 raise ValueError("No image data found. Expecting filename, url, or data.")
573 raise ValueError("No image data found. Expecting filename, url, or data.")
574 elif isinstance(data, string_types) and (
574 elif isinstance(data, string_types) and (
575 data.startswith('http') or _safe_exists(data)
575 data.startswith('http') or _safe_exists(data)
576 ):
576 ):
577 ext = self._find_ext(data)
577 ext = self._find_ext(data)
578 else:
578 else:
579 ext = None
579 ext = None
580
580
581 if ext is not None:
581 if ext is not None:
582 format = ext.lower()
582 format = ext.lower()
583 if ext == u'jpg' or ext == u'jpeg':
583 if ext == u'jpg' or ext == u'jpeg':
584 format = self._FMT_JPEG
584 format = self._FMT_JPEG
585 if ext == u'png':
585 if ext == u'png':
586 format = self._FMT_PNG
586 format = self._FMT_PNG
587 elif isinstance(data, bytes) and format == 'png':
587 elif isinstance(data, bytes) and format == 'png':
588 # infer image type from image data header,
588 # infer image type from image data header,
589 # only if format might not have been specified.
589 # only if format might not have been specified.
590 if data[:2] == _JPEG:
590 if data[:2] == _JPEG:
591 format = 'jpeg'
591 format = 'jpeg'
592
592
593 self.format = unicode(format).lower()
593 self.format = unicode(format).lower()
594 self.embed = embed if embed is not None else (url is None)
594 self.embed = embed if embed is not None else (url is None)
595
595
596 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
596 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
597 raise ValueError("Cannot embed the '%s' image format" % (self.format))
597 raise ValueError("Cannot embed the '%s' image format" % (self.format))
598 self.width = width
598 self.width = width
599 self.height = height
599 self.height = height
600 self.retina = retina
600 self.retina = retina
601 super(Image, self).__init__(data=data, url=url, filename=filename)
601 super(Image, self).__init__(data=data, url=url, filename=filename)
602
602
603 if retina:
603 if retina:
604 self._retina_shape()
604 self._retina_shape()
605
605
606 def _retina_shape(self):
606 def _retina_shape(self):
607 """load pixel-doubled width and height from image data"""
607 """load pixel-doubled width and height from image data"""
608 if not self.embed:
608 if not self.embed:
609 return
609 return
610 if self.format == 'png':
610 if self.format == 'png':
611 w, h = _pngxy(self.data)
611 w, h = _pngxy(self.data)
612 elif self.format == 'jpeg':
612 elif self.format == 'jpeg':
613 w, h = _jpegxy(self.data)
613 w, h = _jpegxy(self.data)
614 else:
614 else:
615 # retina only supports png
615 # retina only supports png
616 return
616 return
617 self.width = w // 2
617 self.width = w // 2
618 self.height = h // 2
618 self.height = h // 2
619
619
620 def reload(self):
620 def reload(self):
621 """Reload the raw data from file or URL."""
621 """Reload the raw data from file or URL."""
622 if self.embed:
622 if self.embed:
623 super(Image,self).reload()
623 super(Image,self).reload()
624 if self.retina:
624 if self.retina:
625 self._retina_shape()
625 self._retina_shape()
626
626
627 def _repr_html_(self):
627 def _repr_html_(self):
628 if not self.embed:
628 if not self.embed:
629 width = height = ''
629 width = height = ''
630 if self.width:
630 if self.width:
631 width = ' width="%d"' % self.width
631 width = ' width="%d"' % self.width
632 if self.height:
632 if self.height:
633 height = ' height="%d"' % self.height
633 height = ' height="%d"' % self.height
634 return u'<img src="%s"%s%s/>' % (self.url, width, height)
634 return u'<img src="%s"%s%s/>' % (self.url, width, height)
635
635
636 def _data_and_metadata(self):
636 def _data_and_metadata(self):
637 """shortcut for returning metadata with shape information, if defined"""
637 """shortcut for returning metadata with shape information, if defined"""
638 md = {}
638 md = {}
639 if self.width:
639 if self.width:
640 md['width'] = self.width
640 md['width'] = self.width
641 if self.height:
641 if self.height:
642 md['height'] = self.height
642 md['height'] = self.height
643 if md:
643 if md:
644 return self.data, md
644 return self.data, md
645 else:
645 else:
646 return self.data
646 return self.data
647
647
648 def _repr_png_(self):
648 def _repr_png_(self):
649 if self.embed and self.format == u'png':
649 if self.embed and self.format == u'png':
650 return self._data_and_metadata()
650 return self._data_and_metadata()
651
651
652 def _repr_jpeg_(self):
652 def _repr_jpeg_(self):
653 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
653 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
654 return self._data_and_metadata()
654 return self._data_and_metadata()
655
655
656 def _find_ext(self, s):
656 def _find_ext(self, s):
657 return unicode(s.split('.')[-1].lower())
657 return unicode(s.split('.')[-1].lower())
658
658
659
659
660 def clear_output(stdout=True, stderr=True, other=True):
660 def clear_output():
661 """Clear the output of the current cell receiving output.
661 """Clear the output of the current cell receiving output."""
662
663 Optionally, each of stdout/stderr or other non-stream data (e.g. anything
664 produced by display()) can be excluded from the clear event.
665
666 By default, everything is cleared.
667
668 Parameters
669 ----------
670 stdout : bool [default: True]
671 Whether to clear stdout.
672 stderr : bool [default: True]
673 Whether to clear stderr.
674 other : bool [default: True]
675 Whether to clear everything else that is not stdout/stderr
676 (e.g. figures,images,HTML, any result of display()).
677 """
678 from IPython.core.interactiveshell import InteractiveShell
662 from IPython.core.interactiveshell import InteractiveShell
679 if InteractiveShell.initialized():
663 if InteractiveShell.initialized():
680 InteractiveShell.instance().display_pub.clear_output(
664 InteractiveShell.instance().display_pub.clear_output()
681 stdout=stdout, stderr=stderr, other=other,
682 )
683 else:
665 else:
684 from IPython.utils import io
666 from IPython.utils import io
685 if stdout:
686 print('\033[2K\r', file=io.stdout, end='')
667 print('\033[2K\r', file=io.stdout, end='')
687 io.stdout.flush()
668 io.stdout.flush()
688 if stderr:
689 print('\033[2K\r', file=io.stderr, end='')
669 print('\033[2K\r', file=io.stderr, end='')
690 io.stderr.flush()
670 io.stderr.flush()
691
671
@@ -1,178 +1,176 b''
1 """An interface for publishing rich data to frontends.
1 """An interface for publishing rich data to frontends.
2
2
3 There are two components of the display system:
3 There are two components of the display system:
4
4
5 * Display formatters, which take a Python object and compute the
5 * Display formatters, which take a Python object and compute the
6 representation of the object in various formats (text, HTML, SVG, etc.).
6 representation of the object in various formats (text, HTML, SVG, etc.).
7 * The display publisher that is used to send the representation data to the
7 * The display publisher that is used to send the representation data to the
8 various frontends.
8 various frontends.
9
9
10 This module defines the logic display publishing. The display publisher uses
10 This module defines the logic display publishing. The display publisher uses
11 the ``display_data`` message type that is defined in the IPython messaging
11 the ``display_data`` message type that is defined in the IPython messaging
12 spec.
12 spec.
13
13
14 Authors:
14 Authors:
15
15
16 * Brian Granger
16 * Brian Granger
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Copyright (C) 2008-2011 The IPython Development Team
20 # Copyright (C) 2008-2011 The IPython Development Team
21 #
21 #
22 # Distributed under the terms of the BSD License. The full license is in
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
23 # the file COPYING, distributed as part of this software.
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Imports
27 # Imports
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 from __future__ import print_function
30 from __future__ import print_function
31
31
32 from IPython.config.configurable import Configurable
32 from IPython.config.configurable import Configurable
33 from IPython.utils import io
33 from IPython.utils import io
34 from IPython.utils.traitlets import List
34 from IPython.utils.traitlets import List
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Main payload class
37 # Main payload class
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 class DisplayPublisher(Configurable):
40 class DisplayPublisher(Configurable):
41 """A traited class that publishes display data to frontends.
41 """A traited class that publishes display data to frontends.
42
42
43 Instances of this class are created by the main IPython object and should
43 Instances of this class are created by the main IPython object and should
44 be accessed there.
44 be accessed there.
45 """
45 """
46
46
47 def _validate_data(self, source, data, metadata=None):
47 def _validate_data(self, source, data, metadata=None):
48 """Validate the display data.
48 """Validate the display data.
49
49
50 Parameters
50 Parameters
51 ----------
51 ----------
52 source : str
52 source : str
53 The fully dotted name of the callable that created the data, like
53 The fully dotted name of the callable that created the data, like
54 :func:`foo.bar.my_formatter`.
54 :func:`foo.bar.my_formatter`.
55 data : dict
55 data : dict
56 The formata data dictionary.
56 The formata data dictionary.
57 metadata : dict
57 metadata : dict
58 Any metadata for the data.
58 Any metadata for the data.
59 """
59 """
60
60
61 if not isinstance(source, basestring):
61 if not isinstance(source, basestring):
62 raise TypeError('source must be a str, got: %r' % source)
62 raise TypeError('source must be a str, got: %r' % source)
63 if not isinstance(data, dict):
63 if not isinstance(data, dict):
64 raise TypeError('data must be a dict, got: %r' % data)
64 raise TypeError('data must be a dict, got: %r' % data)
65 if metadata is not None:
65 if metadata is not None:
66 if not isinstance(metadata, dict):
66 if not isinstance(metadata, dict):
67 raise TypeError('metadata must be a dict, got: %r' % data)
67 raise TypeError('metadata must be a dict, got: %r' % data)
68
68
69 def publish(self, source, data, metadata=None):
69 def publish(self, source, data, metadata=None):
70 """Publish data and metadata to all frontends.
70 """Publish data and metadata to all frontends.
71
71
72 See the ``display_data`` message in the messaging documentation for
72 See the ``display_data`` message in the messaging documentation for
73 more details about this message type.
73 more details about this message type.
74
74
75 The following MIME types are currently implemented:
75 The following MIME types are currently implemented:
76
76
77 * text/plain
77 * text/plain
78 * text/html
78 * text/html
79 * text/latex
79 * text/latex
80 * application/json
80 * application/json
81 * application/javascript
81 * application/javascript
82 * image/png
82 * image/png
83 * image/jpeg
83 * image/jpeg
84 * image/svg+xml
84 * image/svg+xml
85
85
86 Parameters
86 Parameters
87 ----------
87 ----------
88 source : str
88 source : str
89 A string that give the function or method that created the data,
89 A string that give the function or method that created the data,
90 such as 'IPython.core.page'.
90 such as 'IPython.core.page'.
91 data : dict
91 data : dict
92 A dictionary having keys that are valid MIME types (like
92 A dictionary having keys that are valid MIME types (like
93 'text/plain' or 'image/svg+xml') and values that are the data for
93 'text/plain' or 'image/svg+xml') and values that are the data for
94 that MIME type. The data itself must be a JSON'able data
94 that MIME type. The data itself must be a JSON'able data
95 structure. Minimally all data should have the 'text/plain' data,
95 structure. Minimally all data should have the 'text/plain' data,
96 which can be displayed by all frontends. If more than the plain
96 which can be displayed by all frontends. If more than the plain
97 text is given, it is up to the frontend to decide which
97 text is given, it is up to the frontend to decide which
98 representation to use.
98 representation to use.
99 metadata : dict
99 metadata : dict
100 A dictionary for metadata related to the data. This can contain
100 A dictionary for metadata related to the data. This can contain
101 arbitrary key, value pairs that frontends can use to interpret
101 arbitrary key, value pairs that frontends can use to interpret
102 the data. Metadata specific to each mime-type can be specified
102 the data. Metadata specific to each mime-type can be specified
103 in the metadata dict with the same mime-type keys as
103 in the metadata dict with the same mime-type keys as
104 the data itself.
104 the data itself.
105 """
105 """
106
106
107 # The default is to simply write the plain text data using io.stdout.
107 # The default is to simply write the plain text data using io.stdout.
108 if 'text/plain' in data:
108 if 'text/plain' in data:
109 print(data['text/plain'], file=io.stdout)
109 print(data['text/plain'], file=io.stdout)
110
110
111 def clear_output(self, stdout=True, stderr=True, other=True):
111 def clear_output(self):
112 """Clear the output of the cell receiving output."""
112 """Clear the output of the cell receiving output."""
113 if stdout:
114 print('\033[2K\r', file=io.stdout, end='')
113 print('\033[2K\r', file=io.stdout, end='')
115 io.stdout.flush()
114 io.stdout.flush()
116 if stderr:
117 print('\033[2K\r', file=io.stderr, end='')
115 print('\033[2K\r', file=io.stderr, end='')
118 io.stderr.flush()
116 io.stderr.flush()
119
117
120
118
121 class CapturingDisplayPublisher(DisplayPublisher):
119 class CapturingDisplayPublisher(DisplayPublisher):
122 """A DisplayPublisher that stores"""
120 """A DisplayPublisher that stores"""
123 outputs = List()
121 outputs = List()
124
122
125 def publish(self, source, data, metadata=None):
123 def publish(self, source, data, metadata=None):
126 self.outputs.append((source, data, metadata))
124 self.outputs.append((source, data, metadata))
127
125
128 def clear_output(self, stdout=True, stderr=True, other=True):
126 def clear_output(self):
129 super(CapturingDisplayPublisher, self).clear_output(stdout, stderr, other)
127 super(CapturingDisplayPublisher, self).clear_output()
130 if other:
128 if other:
131 # empty the list, *do not* reassign a new list
129 # empty the list, *do not* reassign a new list
132 del self.outputs[:]
130 del self.outputs[:]
133
131
134
132
135 def publish_display_data(source, data, metadata=None):
133 def publish_display_data(source, data, metadata=None):
136 """Publish data and metadata to all frontends.
134 """Publish data and metadata to all frontends.
137
135
138 See the ``display_data`` message in the messaging documentation for
136 See the ``display_data`` message in the messaging documentation for
139 more details about this message type.
137 more details about this message type.
140
138
141 The following MIME types are currently implemented:
139 The following MIME types are currently implemented:
142
140
143 * text/plain
141 * text/plain
144 * text/html
142 * text/html
145 * text/latex
143 * text/latex
146 * application/json
144 * application/json
147 * application/javascript
145 * application/javascript
148 * image/png
146 * image/png
149 * image/jpeg
147 * image/jpeg
150 * image/svg+xml
148 * image/svg+xml
151
149
152 Parameters
150 Parameters
153 ----------
151 ----------
154 source : str
152 source : str
155 A string that give the function or method that created the data,
153 A string that give the function or method that created the data,
156 such as 'IPython.core.page'.
154 such as 'IPython.core.page'.
157 data : dict
155 data : dict
158 A dictionary having keys that are valid MIME types (like
156 A dictionary having keys that are valid MIME types (like
159 'text/plain' or 'image/svg+xml') and values that are the data for
157 'text/plain' or 'image/svg+xml') and values that are the data for
160 that MIME type. The data itself must be a JSON'able data
158 that MIME type. The data itself must be a JSON'able data
161 structure. Minimally all data should have the 'text/plain' data,
159 structure. Minimally all data should have the 'text/plain' data,
162 which can be displayed by all frontends. If more than the plain
160 which can be displayed by all frontends. If more than the plain
163 text is given, it is up to the frontend to decide which
161 text is given, it is up to the frontend to decide which
164 representation to use.
162 representation to use.
165 metadata : dict
163 metadata : dict
166 A dictionary for metadata related to the data. This can contain
164 A dictionary for metadata related to the data. This can contain
167 arbitrary key, value pairs that frontends can use to interpret
165 arbitrary key, value pairs that frontends can use to interpret
168 the data. mime-type keys matching those in data can be used
166 the data. mime-type keys matching those in data can be used
169 to specify metadata about particular representations.
167 to specify metadata about particular representations.
170 """
168 """
171 from IPython.core.interactiveshell import InteractiveShell
169 from IPython.core.interactiveshell import InteractiveShell
172 InteractiveShell.instance().display_pub.publish(
170 InteractiveShell.instance().display_pub.publish(
173 source,
171 source,
174 data,
172 data,
175 metadata
173 metadata
176 )
174 )
177
175
178
176
@@ -1,441 +1,441 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 // CodeCell
9 // CodeCell
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule CodeCell
15 * @submodule CodeCell
16 */
16 */
17
17
18
18
19 /* local util for codemirror */
19 /* local util for codemirror */
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}
21
21
22 /**
22 /**
23 *
23 *
24 * function to delete until previous non blanking space character
24 * function to delete until previous non blanking space character
25 * or first multiple of 4 tabstop.
25 * or first multiple of 4 tabstop.
26 * @private
26 * @private
27 */
27 */
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 if (!posEq(from, to)) {cm.replaceRange("", from, to); return}
30 if (!posEq(from, to)) {cm.replaceRange("", from, to); return}
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 var tabsize = cm.getOption('tabSize');
32 var tabsize = cm.getOption('tabSize');
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 var from = {ch:cur.ch-chToPrevTabStop,line:cur.line}
34 var from = {ch:cur.ch-chToPrevTabStop,line:cur.line}
35 var select = cm.getRange(from,cur)
35 var select = cm.getRange(from,cur)
36 if( select.match(/^\ +$/) != null){
36 if( select.match(/^\ +$/) != null){
37 cm.replaceRange("",from,cur)
37 cm.replaceRange("",from,cur)
38 } else {
38 } else {
39 cm.deleteH(-1,"char")
39 cm.deleteH(-1,"char")
40 }
40 }
41 };
41 };
42
42
43
43
44 var IPython = (function (IPython) {
44 var IPython = (function (IPython) {
45 "use strict";
45 "use strict";
46
46
47 var utils = IPython.utils;
47 var utils = IPython.utils;
48 var key = IPython.utils.keycodes;
48 var key = IPython.utils.keycodes;
49
49
50 /**
50 /**
51 * A Cell conceived to write code.
51 * A Cell conceived to write code.
52 *
52 *
53 * The kernel doesn't have to be set at creation time, in that case
53 * The kernel doesn't have to be set at creation time, in that case
54 * it will be null and set_kernel has to be called later.
54 * it will be null and set_kernel has to be called later.
55 * @class CodeCell
55 * @class CodeCell
56 * @extends IPython.Cell
56 * @extends IPython.Cell
57 *
57 *
58 * @constructor
58 * @constructor
59 * @param {Object|null} kernel
59 * @param {Object|null} kernel
60 * @param {object|undefined} [options]
60 * @param {object|undefined} [options]
61 * @param [options.cm_config] {object} config to pass to CodeMirror
61 * @param [options.cm_config] {object} config to pass to CodeMirror
62 */
62 */
63 var CodeCell = function (kernel, options) {
63 var CodeCell = function (kernel, options) {
64 this.kernel = kernel || null;
64 this.kernel = kernel || null;
65 this.code_mirror = null;
65 this.code_mirror = null;
66 this.input_prompt_number = null;
66 this.input_prompt_number = null;
67 this.collapsed = false;
67 this.collapsed = false;
68 this.cell_type = "code";
68 this.cell_type = "code";
69
69
70
70
71 var cm_overwrite_options = {
71 var cm_overwrite_options = {
72 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
72 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
73 };
73 };
74
74
75 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
75 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
76
76
77 IPython.Cell.apply(this,[options]);
77 IPython.Cell.apply(this,[options]);
78
78
79 var that = this;
79 var that = this;
80 this.element.focusout(
80 this.element.focusout(
81 function() { that.auto_highlight(); }
81 function() { that.auto_highlight(); }
82 );
82 );
83 };
83 };
84
84
85 CodeCell.options_default = {
85 CodeCell.options_default = {
86 cm_config : {
86 cm_config : {
87 extraKeys: {
87 extraKeys: {
88 "Tab" : "indentMore",
88 "Tab" : "indentMore",
89 "Shift-Tab" : "indentLess",
89 "Shift-Tab" : "indentLess",
90 "Backspace" : "delSpaceToPrevTabStop",
90 "Backspace" : "delSpaceToPrevTabStop",
91 "Cmd-/" : "toggleComment",
91 "Cmd-/" : "toggleComment",
92 "Ctrl-/" : "toggleComment"
92 "Ctrl-/" : "toggleComment"
93 },
93 },
94 mode: 'ipython',
94 mode: 'ipython',
95 theme: 'ipython',
95 theme: 'ipython',
96 matchBrackets: true
96 matchBrackets: true
97 }
97 }
98 };
98 };
99
99
100
100
101 CodeCell.prototype = new IPython.Cell();
101 CodeCell.prototype = new IPython.Cell();
102
102
103 /**
103 /**
104 * @method auto_highlight
104 * @method auto_highlight
105 */
105 */
106 CodeCell.prototype.auto_highlight = function () {
106 CodeCell.prototype.auto_highlight = function () {
107 this._auto_highlight(IPython.config.cell_magic_highlight)
107 this._auto_highlight(IPython.config.cell_magic_highlight)
108 };
108 };
109
109
110 /** @method create_element */
110 /** @method create_element */
111 CodeCell.prototype.create_element = function () {
111 CodeCell.prototype.create_element = function () {
112 IPython.Cell.prototype.create_element.apply(this, arguments);
112 IPython.Cell.prototype.create_element.apply(this, arguments);
113
113
114 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
114 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell');
115 cell.attr('tabindex','2');
115 cell.attr('tabindex','2');
116
116
117 this.celltoolbar = new IPython.CellToolbar(this);
117 this.celltoolbar = new IPython.CellToolbar(this);
118
118
119 var input = $('<div></div>').addClass('input');
119 var input = $('<div></div>').addClass('input');
120 var vbox = $('<div/>').addClass('vbox box-flex1')
120 var vbox = $('<div/>').addClass('vbox box-flex1')
121 input.append($('<div/>').addClass('prompt input_prompt'));
121 input.append($('<div/>').addClass('prompt input_prompt'));
122 vbox.append(this.celltoolbar.element);
122 vbox.append(this.celltoolbar.element);
123 var input_area = $('<div/>').addClass('input_area');
123 var input_area = $('<div/>').addClass('input_area');
124 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
124 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
125 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
125 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
126 vbox.append(input_area);
126 vbox.append(input_area);
127 input.append(vbox);
127 input.append(vbox);
128 var output = $('<div></div>');
128 var output = $('<div></div>');
129 cell.append(input).append(output);
129 cell.append(input).append(output);
130 this.element = cell;
130 this.element = cell;
131 this.output_area = new IPython.OutputArea(output, true);
131 this.output_area = new IPython.OutputArea(output, true);
132
132
133 // construct a completer only if class exist
133 // construct a completer only if class exist
134 // otherwise no print view
134 // otherwise no print view
135 if (IPython.Completer !== undefined)
135 if (IPython.Completer !== undefined)
136 {
136 {
137 this.completer = new IPython.Completer(this);
137 this.completer = new IPython.Completer(this);
138 }
138 }
139 };
139 };
140
140
141 /**
141 /**
142 * This method gets called in CodeMirror's onKeyDown/onKeyPress
142 * This method gets called in CodeMirror's onKeyDown/onKeyPress
143 * handlers and is used to provide custom key handling. Its return
143 * handlers and is used to provide custom key handling. Its return
144 * value is used to determine if CodeMirror should ignore the event:
144 * value is used to determine if CodeMirror should ignore the event:
145 * true = ignore, false = don't ignore.
145 * true = ignore, false = don't ignore.
146 * @method handle_codemirror_keyevent
146 * @method handle_codemirror_keyevent
147 */
147 */
148 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
148 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
149
149
150 var that = this;
150 var that = this;
151 // whatever key is pressed, first, cancel the tooltip request before
151 // whatever key is pressed, first, cancel the tooltip request before
152 // they are sent, and remove tooltip if any, except for tab again
152 // they are sent, and remove tooltip if any, except for tab again
153 if (event.type === 'keydown' && event.which != key.TAB ) {
153 if (event.type === 'keydown' && event.which != key.TAB ) {
154 IPython.tooltip.remove_and_cancel_tooltip();
154 IPython.tooltip.remove_and_cancel_tooltip();
155 };
155 };
156
156
157 var cur = editor.getCursor();
157 var cur = editor.getCursor();
158 if (event.keyCode === key.ENTER){
158 if (event.keyCode === key.ENTER){
159 this.auto_highlight();
159 this.auto_highlight();
160 }
160 }
161
161
162 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
162 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
163 // Always ignore shift-enter in CodeMirror as we handle it.
163 // Always ignore shift-enter in CodeMirror as we handle it.
164 return true;
164 return true;
165 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
165 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
166 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
166 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
167 // browser and keyboard layout !
167 // browser and keyboard layout !
168 // Pressing '(' , request tooltip, don't forget to reappend it
168 // Pressing '(' , request tooltip, don't forget to reappend it
169 // The second argument says to hide the tooltip if the docstring
169 // The second argument says to hide the tooltip if the docstring
170 // is actually empty
170 // is actually empty
171 IPython.tooltip.pending(that, true);
171 IPython.tooltip.pending(that, true);
172 } else if (event.which === key.UPARROW && event.type === 'keydown') {
172 } else if (event.which === key.UPARROW && event.type === 'keydown') {
173 // If we are not at the top, let CM handle the up arrow and
173 // If we are not at the top, let CM handle the up arrow and
174 // prevent the global keydown handler from handling it.
174 // prevent the global keydown handler from handling it.
175 if (!that.at_top()) {
175 if (!that.at_top()) {
176 event.stop();
176 event.stop();
177 return false;
177 return false;
178 } else {
178 } else {
179 return true;
179 return true;
180 };
180 };
181 } else if (event.which === key.ESC) {
181 } else if (event.which === key.ESC) {
182 return IPython.tooltip.remove_and_cancel_tooltip(true);
182 return IPython.tooltip.remove_and_cancel_tooltip(true);
183 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
183 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
184 // If we are not at the bottom, let CM handle the down arrow and
184 // If we are not at the bottom, let CM handle the down arrow and
185 // prevent the global keydown handler from handling it.
185 // prevent the global keydown handler from handling it.
186 if (!that.at_bottom()) {
186 if (!that.at_bottom()) {
187 event.stop();
187 event.stop();
188 return false;
188 return false;
189 } else {
189 } else {
190 return true;
190 return true;
191 };
191 };
192 } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) {
192 } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) {
193 if (editor.somethingSelected()){
193 if (editor.somethingSelected()){
194 var anchor = editor.getCursor("anchor");
194 var anchor = editor.getCursor("anchor");
195 var head = editor.getCursor("head");
195 var head = editor.getCursor("head");
196 if( anchor.line != head.line){
196 if( anchor.line != head.line){
197 return false;
197 return false;
198 }
198 }
199 }
199 }
200 IPython.tooltip.request(that);
200 IPython.tooltip.request(that);
201 event.stop();
201 event.stop();
202 return true;
202 return true;
203 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
203 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
204 // Tab completion.
204 // Tab completion.
205 //Do not trim here because of tooltip
205 //Do not trim here because of tooltip
206 if (editor.somethingSelected()){return false}
206 if (editor.somethingSelected()){return false}
207 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
207 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
208 if (pre_cursor.trim() === "") {
208 if (pre_cursor.trim() === "") {
209 // Don't autocomplete if the part of the line before the cursor
209 // Don't autocomplete if the part of the line before the cursor
210 // is empty. In this case, let CodeMirror handle indentation.
210 // is empty. In this case, let CodeMirror handle indentation.
211 return false;
211 return false;
212 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && IPython.config.tooltip_on_tab ) {
212 } else if ((pre_cursor.substr(-1) === "("|| pre_cursor.substr(-1) === " ") && IPython.config.tooltip_on_tab ) {
213 IPython.tooltip.request(that);
213 IPython.tooltip.request(that);
214 // Prevent the event from bubbling up.
214 // Prevent the event from bubbling up.
215 event.stop();
215 event.stop();
216 // Prevent CodeMirror from handling the tab.
216 // Prevent CodeMirror from handling the tab.
217 return true;
217 return true;
218 } else {
218 } else {
219 event.stop();
219 event.stop();
220 this.completer.startCompletion();
220 this.completer.startCompletion();
221 return true;
221 return true;
222 };
222 };
223 } else {
223 } else {
224 // keypress/keyup also trigger on TAB press, and we don't want to
224 // keypress/keyup also trigger on TAB press, and we don't want to
225 // use those to disable tab completion.
225 // use those to disable tab completion.
226 return false;
226 return false;
227 };
227 };
228 return false;
228 return false;
229 };
229 };
230
230
231
231
232 // Kernel related calls.
232 // Kernel related calls.
233
233
234 CodeCell.prototype.set_kernel = function (kernel) {
234 CodeCell.prototype.set_kernel = function (kernel) {
235 this.kernel = kernel;
235 this.kernel = kernel;
236 }
236 }
237
237
238 /**
238 /**
239 * Execute current code cell to the kernel
239 * Execute current code cell to the kernel
240 * @method execute
240 * @method execute
241 */
241 */
242 CodeCell.prototype.execute = function () {
242 CodeCell.prototype.execute = function () {
243 this.output_area.clear_output(true, true, true);
243 this.output_area.clear_output();
244 this.set_input_prompt('*');
244 this.set_input_prompt('*');
245 this.element.addClass("running");
245 this.element.addClass("running");
246 var callbacks = {
246 var callbacks = {
247 'execute_reply': $.proxy(this._handle_execute_reply, this),
247 'execute_reply': $.proxy(this._handle_execute_reply, this),
248 'output': $.proxy(this.output_area.handle_output, this.output_area),
248 'output': $.proxy(this.output_area.handle_output, this.output_area),
249 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
249 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
250 'set_next_input': $.proxy(this._handle_set_next_input, this),
250 'set_next_input': $.proxy(this._handle_set_next_input, this),
251 'input_request': $.proxy(this._handle_input_request, this)
251 'input_request': $.proxy(this._handle_input_request, this)
252 };
252 };
253 var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
253 var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
254 };
254 };
255
255
256 /**
256 /**
257 * @method _handle_execute_reply
257 * @method _handle_execute_reply
258 * @private
258 * @private
259 */
259 */
260 CodeCell.prototype._handle_execute_reply = function (content) {
260 CodeCell.prototype._handle_execute_reply = function (content) {
261 this.set_input_prompt(content.execution_count);
261 this.set_input_prompt(content.execution_count);
262 this.element.removeClass("running");
262 this.element.removeClass("running");
263 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
263 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
264 }
264 }
265
265
266 /**
266 /**
267 * @method _handle_set_next_input
267 * @method _handle_set_next_input
268 * @private
268 * @private
269 */
269 */
270 CodeCell.prototype._handle_set_next_input = function (text) {
270 CodeCell.prototype._handle_set_next_input = function (text) {
271 var data = {'cell': this, 'text': text}
271 var data = {'cell': this, 'text': text}
272 $([IPython.events]).trigger('set_next_input.Notebook', data);
272 $([IPython.events]).trigger('set_next_input.Notebook', data);
273 }
273 }
274
274
275 /**
275 /**
276 * @method _handle_input_request
276 * @method _handle_input_request
277 * @private
277 * @private
278 */
278 */
279 CodeCell.prototype._handle_input_request = function (content) {
279 CodeCell.prototype._handle_input_request = function (content) {
280 this.output_area.append_raw_input(content);
280 this.output_area.append_raw_input(content);
281 }
281 }
282
282
283
283
284 // Basic cell manipulation.
284 // Basic cell manipulation.
285
285
286 CodeCell.prototype.select = function () {
286 CodeCell.prototype.select = function () {
287 IPython.Cell.prototype.select.apply(this);
287 IPython.Cell.prototype.select.apply(this);
288 this.code_mirror.refresh();
288 this.code_mirror.refresh();
289 this.code_mirror.focus();
289 this.code_mirror.focus();
290 this.auto_highlight();
290 this.auto_highlight();
291 // We used to need an additional refresh() after the focus, but
291 // We used to need an additional refresh() after the focus, but
292 // it appears that this has been fixed in CM. This bug would show
292 // it appears that this has been fixed in CM. This bug would show
293 // up on FF when a newly loaded markdown cell was edited.
293 // up on FF when a newly loaded markdown cell was edited.
294 };
294 };
295
295
296
296
297 CodeCell.prototype.select_all = function () {
297 CodeCell.prototype.select_all = function () {
298 var start = {line: 0, ch: 0};
298 var start = {line: 0, ch: 0};
299 var nlines = this.code_mirror.lineCount();
299 var nlines = this.code_mirror.lineCount();
300 var last_line = this.code_mirror.getLine(nlines-1);
300 var last_line = this.code_mirror.getLine(nlines-1);
301 var end = {line: nlines-1, ch: last_line.length};
301 var end = {line: nlines-1, ch: last_line.length};
302 this.code_mirror.setSelection(start, end);
302 this.code_mirror.setSelection(start, end);
303 };
303 };
304
304
305
305
306 CodeCell.prototype.collapse = function () {
306 CodeCell.prototype.collapse = function () {
307 this.collapsed = true;
307 this.collapsed = true;
308 this.output_area.collapse();
308 this.output_area.collapse();
309 };
309 };
310
310
311
311
312 CodeCell.prototype.expand = function () {
312 CodeCell.prototype.expand = function () {
313 this.collapsed = false;
313 this.collapsed = false;
314 this.output_area.expand();
314 this.output_area.expand();
315 };
315 };
316
316
317
317
318 CodeCell.prototype.toggle_output = function () {
318 CodeCell.prototype.toggle_output = function () {
319 this.collapsed = Boolean(1 - this.collapsed);
319 this.collapsed = Boolean(1 - this.collapsed);
320 this.output_area.toggle_output();
320 this.output_area.toggle_output();
321 };
321 };
322
322
323
323
324 CodeCell.prototype.toggle_output_scroll = function () {
324 CodeCell.prototype.toggle_output_scroll = function () {
325 this.output_area.toggle_scroll();
325 this.output_area.toggle_scroll();
326 };
326 };
327
327
328
328
329 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
329 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
330 var ns = prompt_value || "&nbsp;";
330 var ns = prompt_value || "&nbsp;";
331 return 'In&nbsp;[' + ns + ']:'
331 return 'In&nbsp;[' + ns + ']:'
332 };
332 };
333
333
334 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
334 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
335 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
335 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
336 for(var i=1; i < lines_number; i++){html.push(['...:'])};
336 for(var i=1; i < lines_number; i++){html.push(['...:'])};
337 return html.join('</br>')
337 return html.join('</br>')
338 };
338 };
339
339
340 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
340 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
341
341
342
342
343 CodeCell.prototype.set_input_prompt = function (number) {
343 CodeCell.prototype.set_input_prompt = function (number) {
344 var nline = 1
344 var nline = 1
345 if( this.code_mirror != undefined) {
345 if( this.code_mirror != undefined) {
346 nline = this.code_mirror.lineCount();
346 nline = this.code_mirror.lineCount();
347 }
347 }
348 this.input_prompt_number = number;
348 this.input_prompt_number = number;
349 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
349 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
350 this.element.find('div.input_prompt').html(prompt_html);
350 this.element.find('div.input_prompt').html(prompt_html);
351 };
351 };
352
352
353
353
354 CodeCell.prototype.clear_input = function () {
354 CodeCell.prototype.clear_input = function () {
355 this.code_mirror.setValue('');
355 this.code_mirror.setValue('');
356 };
356 };
357
357
358
358
359 CodeCell.prototype.get_text = function () {
359 CodeCell.prototype.get_text = function () {
360 return this.code_mirror.getValue();
360 return this.code_mirror.getValue();
361 };
361 };
362
362
363
363
364 CodeCell.prototype.set_text = function (code) {
364 CodeCell.prototype.set_text = function (code) {
365 return this.code_mirror.setValue(code);
365 return this.code_mirror.setValue(code);
366 };
366 };
367
367
368
368
369 CodeCell.prototype.at_top = function () {
369 CodeCell.prototype.at_top = function () {
370 var cursor = this.code_mirror.getCursor();
370 var cursor = this.code_mirror.getCursor();
371 if (cursor.line === 0 && cursor.ch === 0) {
371 if (cursor.line === 0 && cursor.ch === 0) {
372 return true;
372 return true;
373 } else {
373 } else {
374 return false;
374 return false;
375 }
375 }
376 };
376 };
377
377
378
378
379 CodeCell.prototype.at_bottom = function () {
379 CodeCell.prototype.at_bottom = function () {
380 var cursor = this.code_mirror.getCursor();
380 var cursor = this.code_mirror.getCursor();
381 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
381 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
382 return true;
382 return true;
383 } else {
383 } else {
384 return false;
384 return false;
385 }
385 }
386 };
386 };
387
387
388
388
389 CodeCell.prototype.clear_output = function (stdout, stderr, other) {
389 CodeCell.prototype.clear_output = function () {
390 this.output_area.clear_output(stdout, stderr, other);
390 this.output_area.clear_output();
391 };
391 };
392
392
393
393
394 // JSON serialization
394 // JSON serialization
395
395
396 CodeCell.prototype.fromJSON = function (data) {
396 CodeCell.prototype.fromJSON = function (data) {
397 IPython.Cell.prototype.fromJSON.apply(this, arguments);
397 IPython.Cell.prototype.fromJSON.apply(this, arguments);
398 if (data.cell_type === 'code') {
398 if (data.cell_type === 'code') {
399 if (data.input !== undefined) {
399 if (data.input !== undefined) {
400 this.set_text(data.input);
400 this.set_text(data.input);
401 // make this value the starting point, so that we can only undo
401 // make this value the starting point, so that we can only undo
402 // to this state, instead of a blank cell
402 // to this state, instead of a blank cell
403 this.code_mirror.clearHistory();
403 this.code_mirror.clearHistory();
404 this.auto_highlight();
404 this.auto_highlight();
405 }
405 }
406 if (data.prompt_number !== undefined) {
406 if (data.prompt_number !== undefined) {
407 this.set_input_prompt(data.prompt_number);
407 this.set_input_prompt(data.prompt_number);
408 } else {
408 } else {
409 this.set_input_prompt();
409 this.set_input_prompt();
410 };
410 };
411 this.output_area.fromJSON(data.outputs);
411 this.output_area.fromJSON(data.outputs);
412 if (data.collapsed !== undefined) {
412 if (data.collapsed !== undefined) {
413 if (data.collapsed) {
413 if (data.collapsed) {
414 this.collapse();
414 this.collapse();
415 } else {
415 } else {
416 this.expand();
416 this.expand();
417 };
417 };
418 };
418 };
419 };
419 };
420 };
420 };
421
421
422
422
423 CodeCell.prototype.toJSON = function () {
423 CodeCell.prototype.toJSON = function () {
424 var data = IPython.Cell.prototype.toJSON.apply(this);
424 var data = IPython.Cell.prototype.toJSON.apply(this);
425 data.input = this.get_text();
425 data.input = this.get_text();
426 data.cell_type = 'code';
426 data.cell_type = 'code';
427 if (this.input_prompt_number) {
427 if (this.input_prompt_number) {
428 data.prompt_number = this.input_prompt_number;
428 data.prompt_number = this.input_prompt_number;
429 };
429 };
430 var outputs = this.output_area.toJSON();
430 var outputs = this.output_area.toJSON();
431 data.outputs = outputs;
431 data.outputs = outputs;
432 data.language = 'python';
432 data.language = 'python';
433 data.collapsed = this.collapsed;
433 data.collapsed = this.collapsed;
434 return data;
434 return data;
435 };
435 };
436
436
437
437
438 IPython.CodeCell = CodeCell;
438 IPython.CodeCell = CodeCell;
439
439
440 return IPython;
440 return IPython;
441 }(IPython));
441 }(IPython));
@@ -1,2077 +1,2077 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 // 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 var key = IPython.utils.keycodes;
16 var key = IPython.utils.keycodes;
17
17
18 /**
18 /**
19 * A notebook contains and manages cells.
19 * A notebook contains and manages cells.
20 *
20 *
21 * @class Notebook
21 * @class Notebook
22 * @constructor
22 * @constructor
23 * @param {String} selector A jQuery selector for the notebook's DOM element
23 * @param {String} selector A jQuery selector for the notebook's DOM element
24 * @param {Object} [options] A config object
24 * @param {Object} [options] A config object
25 */
25 */
26 var Notebook = function (selector, options) {
26 var Notebook = function (selector, options) {
27 var options = options || {};
27 var options = options || {};
28 this._baseProjectUrl = options.baseProjectUrl;
28 this._baseProjectUrl = options.baseProjectUrl;
29
29
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.kernel = null;
34 this.kernel = null;
35 this.clipboard = null;
35 this.clipboard = null;
36 this.undelete_backup = null;
36 this.undelete_backup = null;
37 this.undelete_index = null;
37 this.undelete_index = null;
38 this.undelete_below = false;
38 this.undelete_below = false;
39 this.paste_enabled = false;
39 this.paste_enabled = false;
40 this.set_dirty(false);
40 this.set_dirty(false);
41 this.metadata = {};
41 this.metadata = {};
42 this._checkpoint_after_save = false;
42 this._checkpoint_after_save = false;
43 this.last_checkpoint = null;
43 this.last_checkpoint = null;
44 this.checkpoints = [];
44 this.checkpoints = [];
45 this.autosave_interval = 0;
45 this.autosave_interval = 0;
46 this.autosave_timer = null;
46 this.autosave_timer = null;
47 // autosave *at most* every two minutes
47 // autosave *at most* every two minutes
48 this.minimum_autosave_interval = 120000;
48 this.minimum_autosave_interval = 120000;
49 // single worksheet for now
49 // single worksheet for now
50 this.worksheet_metadata = {};
50 this.worksheet_metadata = {};
51 this.control_key_active = false;
51 this.control_key_active = false;
52 this.notebook_id = null;
52 this.notebook_id = null;
53 this.notebook_name = null;
53 this.notebook_name = null;
54 this.notebook_name_blacklist_re = /[\/\\:]/;
54 this.notebook_name_blacklist_re = /[\/\\:]/;
55 this.nbformat = 3 // Increment this when changing the nbformat
55 this.nbformat = 3 // Increment this when changing the nbformat
56 this.nbformat_minor = 0 // Increment this when changing the nbformat
56 this.nbformat_minor = 0 // Increment this when changing the nbformat
57 this.style();
57 this.style();
58 this.create_elements();
58 this.create_elements();
59 this.bind_events();
59 this.bind_events();
60 };
60 };
61
61
62 /**
62 /**
63 * Tweak the notebook's CSS style.
63 * Tweak the notebook's CSS style.
64 *
64 *
65 * @method style
65 * @method style
66 */
66 */
67 Notebook.prototype.style = function () {
67 Notebook.prototype.style = function () {
68 $('div#notebook').addClass('border-box-sizing');
68 $('div#notebook').addClass('border-box-sizing');
69 };
69 };
70
70
71 /**
71 /**
72 * Get the root URL of the notebook server.
72 * Get the root URL of the notebook server.
73 *
73 *
74 * @method baseProjectUrl
74 * @method baseProjectUrl
75 * @return {String} The base project URL
75 * @return {String} The base project URL
76 */
76 */
77 Notebook.prototype.baseProjectUrl = function(){
77 Notebook.prototype.baseProjectUrl = function(){
78 return this._baseProjectUrl || $('body').data('baseProjectUrl');
78 return this._baseProjectUrl || $('body').data('baseProjectUrl');
79 };
79 };
80
80
81 /**
81 /**
82 * Create an HTML and CSS representation of the notebook.
82 * Create an HTML and CSS representation of the notebook.
83 *
83 *
84 * @method create_elements
84 * @method create_elements
85 */
85 */
86 Notebook.prototype.create_elements = function () {
86 Notebook.prototype.create_elements = function () {
87 // We add this end_space div to the end of the notebook div to:
87 // We add this end_space div to the end of the notebook div to:
88 // i) provide a margin between the last cell and the end of the notebook
88 // i) provide a margin between the last cell and the end of the notebook
89 // ii) to prevent the div from scrolling up when the last cell is being
89 // ii) to prevent the div from scrolling up when the last cell is being
90 // edited, but is too low on the page, which browsers will do automatically.
90 // edited, but is too low on the page, which browsers will do automatically.
91 var that = this;
91 var that = this;
92 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
92 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
93 var end_space = $('<div/>').addClass('end_space');
93 var end_space = $('<div/>').addClass('end_space');
94 end_space.dblclick(function (e) {
94 end_space.dblclick(function (e) {
95 var ncells = that.ncells();
95 var ncells = that.ncells();
96 that.insert_cell_below('code',ncells-1);
96 that.insert_cell_below('code',ncells-1);
97 });
97 });
98 this.element.append(this.container);
98 this.element.append(this.container);
99 this.container.append(end_space);
99 this.container.append(end_space);
100 $('div#notebook').addClass('border-box-sizing');
100 $('div#notebook').addClass('border-box-sizing');
101 };
101 };
102
102
103 /**
103 /**
104 * Bind JavaScript events: key presses and custom IPython events.
104 * Bind JavaScript events: key presses and custom IPython events.
105 *
105 *
106 * @method bind_events
106 * @method bind_events
107 */
107 */
108 Notebook.prototype.bind_events = function () {
108 Notebook.prototype.bind_events = function () {
109 var that = this;
109 var that = this;
110
110
111 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
111 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
112 var index = that.find_cell_index(data.cell);
112 var index = that.find_cell_index(data.cell);
113 var new_cell = that.insert_cell_below('code',index);
113 var new_cell = that.insert_cell_below('code',index);
114 new_cell.set_text(data.text);
114 new_cell.set_text(data.text);
115 that.dirty = true;
115 that.dirty = true;
116 });
116 });
117
117
118 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
118 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
119 that.dirty = data.value;
119 that.dirty = data.value;
120 });
120 });
121
121
122 $([IPython.events]).on('select.Cell', function (event, data) {
122 $([IPython.events]).on('select.Cell', function (event, data) {
123 var index = that.find_cell_index(data.cell);
123 var index = that.find_cell_index(data.cell);
124 that.select(index);
124 that.select(index);
125 });
125 });
126
126
127 $([IPython.events]).on('status_autorestarting.Kernel', function () {
127 $([IPython.events]).on('status_autorestarting.Kernel', function () {
128 IPython.dialog.modal({
128 IPython.dialog.modal({
129 title: "Kernel Restarting",
129 title: "Kernel Restarting",
130 body: "The kernel appears to have died. It will restart automatically.",
130 body: "The kernel appears to have died. It will restart automatically.",
131 buttons: {
131 buttons: {
132 OK : {
132 OK : {
133 class : "btn-primary"
133 class : "btn-primary"
134 }
134 }
135 }
135 }
136 });
136 });
137 });
137 });
138
138
139
139
140 $(document).keydown(function (event) {
140 $(document).keydown(function (event) {
141
141
142 // Save (CTRL+S) or (AppleKey+S)
142 // Save (CTRL+S) or (AppleKey+S)
143 //metaKey = applekey on mac
143 //metaKey = applekey on mac
144 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
144 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
145 that.save_checkpoint();
145 that.save_checkpoint();
146 event.preventDefault();
146 event.preventDefault();
147 return false;
147 return false;
148 } else if (event.which === key.ESC) {
148 } else if (event.which === key.ESC) {
149 // Intercept escape at highest level to avoid closing
149 // Intercept escape at highest level to avoid closing
150 // websocket connection with firefox
150 // websocket connection with firefox
151 IPython.pager.collapse();
151 IPython.pager.collapse();
152 event.preventDefault();
152 event.preventDefault();
153 } else if (event.which === key.SHIFT) {
153 } else if (event.which === key.SHIFT) {
154 // ignore shift keydown
154 // ignore shift keydown
155 return true;
155 return true;
156 }
156 }
157 if (event.which === key.UPARROW && !event.shiftKey) {
157 if (event.which === key.UPARROW && !event.shiftKey) {
158 var cell = that.get_selected_cell();
158 var cell = that.get_selected_cell();
159 if (cell && cell.at_top()) {
159 if (cell && cell.at_top()) {
160 event.preventDefault();
160 event.preventDefault();
161 that.select_prev();
161 that.select_prev();
162 };
162 };
163 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
163 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
164 var cell = that.get_selected_cell();
164 var cell = that.get_selected_cell();
165 if (cell && cell.at_bottom()) {
165 if (cell && cell.at_bottom()) {
166 event.preventDefault();
166 event.preventDefault();
167 that.select_next();
167 that.select_next();
168 };
168 };
169 } else if (event.which === key.ENTER && event.shiftKey) {
169 } else if (event.which === key.ENTER && event.shiftKey) {
170 that.execute_selected_cell();
170 that.execute_selected_cell();
171 return false;
171 return false;
172 } else if (event.which === key.ENTER && event.altKey) {
172 } else if (event.which === key.ENTER && event.altKey) {
173 // Execute code cell, and insert new in place
173 // Execute code cell, and insert new in place
174 that.execute_selected_cell();
174 that.execute_selected_cell();
175 // Only insert a new cell, if we ended up in an already populated cell
175 // Only insert a new cell, if we ended up in an already populated cell
176 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
176 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
177 that.insert_cell_above('code');
177 that.insert_cell_above('code');
178 }
178 }
179 return false;
179 return false;
180 } else if (event.which === key.ENTER && event.ctrlKey) {
180 } else if (event.which === key.ENTER && event.ctrlKey) {
181 that.execute_selected_cell({terminal:true});
181 that.execute_selected_cell({terminal:true});
182 return false;
182 return false;
183 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
183 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
184 that.control_key_active = true;
184 that.control_key_active = true;
185 return false;
185 return false;
186 } else if (event.which === 88 && that.control_key_active) {
186 } else if (event.which === 88 && that.control_key_active) {
187 // Cut selected cell = x
187 // Cut selected cell = x
188 that.cut_cell();
188 that.cut_cell();
189 that.control_key_active = false;
189 that.control_key_active = false;
190 return false;
190 return false;
191 } else if (event.which === 67 && that.control_key_active) {
191 } else if (event.which === 67 && that.control_key_active) {
192 // Copy selected cell = c
192 // Copy selected cell = c
193 that.copy_cell();
193 that.copy_cell();
194 that.control_key_active = false;
194 that.control_key_active = false;
195 return false;
195 return false;
196 } else if (event.which === 86 && that.control_key_active) {
196 } else if (event.which === 86 && that.control_key_active) {
197 // Paste below selected cell = v
197 // Paste below selected cell = v
198 that.paste_cell_below();
198 that.paste_cell_below();
199 that.control_key_active = false;
199 that.control_key_active = false;
200 return false;
200 return false;
201 } else if (event.which === 68 && that.control_key_active) {
201 } else if (event.which === 68 && that.control_key_active) {
202 // Delete selected cell = d
202 // Delete selected cell = d
203 that.delete_cell();
203 that.delete_cell();
204 that.control_key_active = false;
204 that.control_key_active = false;
205 return false;
205 return false;
206 } else if (event.which === 65 && that.control_key_active) {
206 } else if (event.which === 65 && that.control_key_active) {
207 // Insert code cell above selected = a
207 // Insert code cell above selected = a
208 that.insert_cell_above('code');
208 that.insert_cell_above('code');
209 that.control_key_active = false;
209 that.control_key_active = false;
210 return false;
210 return false;
211 } else if (event.which === 66 && that.control_key_active) {
211 } else if (event.which === 66 && that.control_key_active) {
212 // Insert code cell below selected = b
212 // Insert code cell below selected = b
213 that.insert_cell_below('code');
213 that.insert_cell_below('code');
214 that.control_key_active = false;
214 that.control_key_active = false;
215 return false;
215 return false;
216 } else if (event.which === 89 && that.control_key_active) {
216 } else if (event.which === 89 && that.control_key_active) {
217 // To code = y
217 // To code = y
218 that.to_code();
218 that.to_code();
219 that.control_key_active = false;
219 that.control_key_active = false;
220 return false;
220 return false;
221 } else if (event.which === 77 && that.control_key_active) {
221 } else if (event.which === 77 && that.control_key_active) {
222 // To markdown = m
222 // To markdown = m
223 that.to_markdown();
223 that.to_markdown();
224 that.control_key_active = false;
224 that.control_key_active = false;
225 return false;
225 return false;
226 } else if (event.which === 84 && that.control_key_active) {
226 } else if (event.which === 84 && that.control_key_active) {
227 // To Raw = t
227 // To Raw = t
228 that.to_raw();
228 that.to_raw();
229 that.control_key_active = false;
229 that.control_key_active = false;
230 return false;
230 return false;
231 } else if (event.which === 49 && that.control_key_active) {
231 } else if (event.which === 49 && that.control_key_active) {
232 // To Heading 1 = 1
232 // To Heading 1 = 1
233 that.to_heading(undefined, 1);
233 that.to_heading(undefined, 1);
234 that.control_key_active = false;
234 that.control_key_active = false;
235 return false;
235 return false;
236 } else if (event.which === 50 && that.control_key_active) {
236 } else if (event.which === 50 && that.control_key_active) {
237 // To Heading 2 = 2
237 // To Heading 2 = 2
238 that.to_heading(undefined, 2);
238 that.to_heading(undefined, 2);
239 that.control_key_active = false;
239 that.control_key_active = false;
240 return false;
240 return false;
241 } else if (event.which === 51 && that.control_key_active) {
241 } else if (event.which === 51 && that.control_key_active) {
242 // To Heading 3 = 3
242 // To Heading 3 = 3
243 that.to_heading(undefined, 3);
243 that.to_heading(undefined, 3);
244 that.control_key_active = false;
244 that.control_key_active = false;
245 return false;
245 return false;
246 } else if (event.which === 52 && that.control_key_active) {
246 } else if (event.which === 52 && that.control_key_active) {
247 // To Heading 4 = 4
247 // To Heading 4 = 4
248 that.to_heading(undefined, 4);
248 that.to_heading(undefined, 4);
249 that.control_key_active = false;
249 that.control_key_active = false;
250 return false;
250 return false;
251 } else if (event.which === 53 && that.control_key_active) {
251 } else if (event.which === 53 && that.control_key_active) {
252 // To Heading 5 = 5
252 // To Heading 5 = 5
253 that.to_heading(undefined, 5);
253 that.to_heading(undefined, 5);
254 that.control_key_active = false;
254 that.control_key_active = false;
255 return false;
255 return false;
256 } else if (event.which === 54 && that.control_key_active) {
256 } else if (event.which === 54 && that.control_key_active) {
257 // To Heading 6 = 6
257 // To Heading 6 = 6
258 that.to_heading(undefined, 6);
258 that.to_heading(undefined, 6);
259 that.control_key_active = false;
259 that.control_key_active = false;
260 return false;
260 return false;
261 } else if (event.which === 79 && that.control_key_active) {
261 } else if (event.which === 79 && that.control_key_active) {
262 // Toggle output = o
262 // Toggle output = o
263 if (event.shiftKey){
263 if (event.shiftKey){
264 that.toggle_output_scroll();
264 that.toggle_output_scroll();
265 } else {
265 } else {
266 that.toggle_output();
266 that.toggle_output();
267 }
267 }
268 that.control_key_active = false;
268 that.control_key_active = false;
269 return false;
269 return false;
270 } else if (event.which === 83 && that.control_key_active) {
270 } else if (event.which === 83 && that.control_key_active) {
271 // Save notebook = s
271 // Save notebook = s
272 that.save_checkpoint();
272 that.save_checkpoint();
273 that.control_key_active = false;
273 that.control_key_active = false;
274 return false;
274 return false;
275 } else if (event.which === 74 && that.control_key_active) {
275 } else if (event.which === 74 && that.control_key_active) {
276 // Move cell down = j
276 // Move cell down = j
277 that.move_cell_down();
277 that.move_cell_down();
278 that.control_key_active = false;
278 that.control_key_active = false;
279 return false;
279 return false;
280 } else if (event.which === 75 && that.control_key_active) {
280 } else if (event.which === 75 && that.control_key_active) {
281 // Move cell up = k
281 // Move cell up = k
282 that.move_cell_up();
282 that.move_cell_up();
283 that.control_key_active = false;
283 that.control_key_active = false;
284 return false;
284 return false;
285 } else if (event.which === 80 && that.control_key_active) {
285 } else if (event.which === 80 && that.control_key_active) {
286 // Select previous = p
286 // Select previous = p
287 that.select_prev();
287 that.select_prev();
288 that.control_key_active = false;
288 that.control_key_active = false;
289 return false;
289 return false;
290 } else if (event.which === 78 && that.control_key_active) {
290 } else if (event.which === 78 && that.control_key_active) {
291 // Select next = n
291 // Select next = n
292 that.select_next();
292 that.select_next();
293 that.control_key_active = false;
293 that.control_key_active = false;
294 return false;
294 return false;
295 } else if (event.which === 76 && that.control_key_active) {
295 } else if (event.which === 76 && that.control_key_active) {
296 // Toggle line numbers = l
296 // Toggle line numbers = l
297 that.cell_toggle_line_numbers();
297 that.cell_toggle_line_numbers();
298 that.control_key_active = false;
298 that.control_key_active = false;
299 return false;
299 return false;
300 } else if (event.which === 73 && that.control_key_active) {
300 } else if (event.which === 73 && that.control_key_active) {
301 // Interrupt kernel = i
301 // Interrupt kernel = i
302 that.kernel.interrupt();
302 that.kernel.interrupt();
303 that.control_key_active = false;
303 that.control_key_active = false;
304 return false;
304 return false;
305 } else if (event.which === 190 && that.control_key_active) {
305 } else if (event.which === 190 && that.control_key_active) {
306 // Restart kernel = . # matches qt console
306 // Restart kernel = . # matches qt console
307 that.restart_kernel();
307 that.restart_kernel();
308 that.control_key_active = false;
308 that.control_key_active = false;
309 return false;
309 return false;
310 } else if (event.which === 72 && that.control_key_active) {
310 } else if (event.which === 72 && that.control_key_active) {
311 // Show keyboard shortcuts = h
311 // Show keyboard shortcuts = h
312 IPython.quick_help.show_keyboard_shortcuts();
312 IPython.quick_help.show_keyboard_shortcuts();
313 that.control_key_active = false;
313 that.control_key_active = false;
314 return false;
314 return false;
315 } else if (event.which === 90 && that.control_key_active) {
315 } else if (event.which === 90 && that.control_key_active) {
316 // Undo last cell delete = z
316 // Undo last cell delete = z
317 that.undelete();
317 that.undelete();
318 that.control_key_active = false;
318 that.control_key_active = false;
319 return false;
319 return false;
320 } else if (event.which === 189 && that.control_key_active) {
320 } else if (event.which === 189 && that.control_key_active) {
321 // Split cell = -
321 // Split cell = -
322 that.split_cell();
322 that.split_cell();
323 that.control_key_active = false;
323 that.control_key_active = false;
324 return false;
324 return false;
325 } else if (that.control_key_active) {
325 } else if (that.control_key_active) {
326 that.control_key_active = false;
326 that.control_key_active = false;
327 return true;
327 return true;
328 }
328 }
329 return true;
329 return true;
330 });
330 });
331
331
332 var collapse_time = function(time){
332 var collapse_time = function(time){
333 var app_height = $('#ipython-main-app').height(); // content height
333 var app_height = $('#ipython-main-app').height(); // content height
334 var splitter_height = $('div#pager_splitter').outerHeight(true);
334 var splitter_height = $('div#pager_splitter').outerHeight(true);
335 var new_height = app_height - splitter_height;
335 var new_height = app_height - splitter_height;
336 that.element.animate({height : new_height + 'px'}, time);
336 that.element.animate({height : new_height + 'px'}, time);
337 }
337 }
338
338
339 this.element.bind('collapse_pager', function (event,extrap) {
339 this.element.bind('collapse_pager', function (event,extrap) {
340 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
340 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
341 collapse_time(time);
341 collapse_time(time);
342 });
342 });
343
343
344 var expand_time = function(time) {
344 var expand_time = function(time) {
345 var app_height = $('#ipython-main-app').height(); // content height
345 var app_height = $('#ipython-main-app').height(); // content height
346 var splitter_height = $('div#pager_splitter').outerHeight(true);
346 var splitter_height = $('div#pager_splitter').outerHeight(true);
347 var pager_height = $('div#pager').outerHeight(true);
347 var pager_height = $('div#pager').outerHeight(true);
348 var new_height = app_height - pager_height - splitter_height;
348 var new_height = app_height - pager_height - splitter_height;
349 that.element.animate({height : new_height + 'px'}, time);
349 that.element.animate({height : new_height + 'px'}, time);
350 }
350 }
351
351
352 this.element.bind('expand_pager', function (event, extrap) {
352 this.element.bind('expand_pager', function (event, extrap) {
353 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
353 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
354 expand_time(time);
354 expand_time(time);
355 });
355 });
356
356
357 // Firefox 22 broke $(window).on("beforeunload")
357 // Firefox 22 broke $(window).on("beforeunload")
358 // I'm not sure why or how.
358 // I'm not sure why or how.
359 window.onbeforeunload = function (e) {
359 window.onbeforeunload = function (e) {
360 // TODO: Make killing the kernel configurable.
360 // TODO: Make killing the kernel configurable.
361 var kill_kernel = false;
361 var kill_kernel = false;
362 if (kill_kernel) {
362 if (kill_kernel) {
363 that.kernel.kill();
363 that.kernel.kill();
364 }
364 }
365 // if we are autosaving, trigger an autosave on nav-away.
365 // if we are autosaving, trigger an autosave on nav-away.
366 // still warn, because if we don't the autosave may fail.
366 // still warn, because if we don't the autosave may fail.
367 if (that.dirty) {
367 if (that.dirty) {
368 if ( that.autosave_interval ) {
368 if ( that.autosave_interval ) {
369 // schedule autosave in a timeout
369 // schedule autosave in a timeout
370 // this gives you a chance to forcefully discard changes
370 // this gives you a chance to forcefully discard changes
371 // by reloading the page if you *really* want to.
371 // by reloading the page if you *really* want to.
372 // the timer doesn't start until you *dismiss* the dialog.
372 // the timer doesn't start until you *dismiss* the dialog.
373 setTimeout(function () {
373 setTimeout(function () {
374 if (that.dirty) {
374 if (that.dirty) {
375 that.save_notebook();
375 that.save_notebook();
376 }
376 }
377 }, 1000);
377 }, 1000);
378 return "Autosave in progress, latest changes may be lost.";
378 return "Autosave in progress, latest changes may be lost.";
379 } else {
379 } else {
380 return "Unsaved changes will be lost.";
380 return "Unsaved changes will be lost.";
381 }
381 }
382 };
382 };
383 // Null is the *only* return value that will make the browser not
383 // Null is the *only* return value that will make the browser not
384 // pop up the "don't leave" dialog.
384 // pop up the "don't leave" dialog.
385 return null;
385 return null;
386 };
386 };
387 };
387 };
388
388
389 /**
389 /**
390 * Set the dirty flag, and trigger the set_dirty.Notebook event
390 * Set the dirty flag, and trigger the set_dirty.Notebook event
391 *
391 *
392 * @method set_dirty
392 * @method set_dirty
393 */
393 */
394 Notebook.prototype.set_dirty = function (value) {
394 Notebook.prototype.set_dirty = function (value) {
395 if (value === undefined) {
395 if (value === undefined) {
396 value = true;
396 value = true;
397 }
397 }
398 if (this.dirty == value) {
398 if (this.dirty == value) {
399 return;
399 return;
400 }
400 }
401 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
401 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
402 };
402 };
403
403
404 /**
404 /**
405 * Scroll the top of the page to a given cell.
405 * Scroll the top of the page to a given cell.
406 *
406 *
407 * @method scroll_to_cell
407 * @method scroll_to_cell
408 * @param {Number} cell_number An index of the cell to view
408 * @param {Number} cell_number An index of the cell to view
409 * @param {Number} time Animation time in milliseconds
409 * @param {Number} time Animation time in milliseconds
410 * @return {Number} Pixel offset from the top of the container
410 * @return {Number} Pixel offset from the top of the container
411 */
411 */
412 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
412 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
413 var cells = this.get_cells();
413 var cells = this.get_cells();
414 var time = time || 0;
414 var time = time || 0;
415 cell_number = Math.min(cells.length-1,cell_number);
415 cell_number = Math.min(cells.length-1,cell_number);
416 cell_number = Math.max(0 ,cell_number);
416 cell_number = Math.max(0 ,cell_number);
417 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
417 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
418 this.element.animate({scrollTop:scroll_value}, time);
418 this.element.animate({scrollTop:scroll_value}, time);
419 return scroll_value;
419 return scroll_value;
420 };
420 };
421
421
422 /**
422 /**
423 * Scroll to the bottom of the page.
423 * Scroll to the bottom of the page.
424 *
424 *
425 * @method scroll_to_bottom
425 * @method scroll_to_bottom
426 */
426 */
427 Notebook.prototype.scroll_to_bottom = function () {
427 Notebook.prototype.scroll_to_bottom = function () {
428 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
428 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
429 };
429 };
430
430
431 /**
431 /**
432 * Scroll to the top of the page.
432 * Scroll to the top of the page.
433 *
433 *
434 * @method scroll_to_top
434 * @method scroll_to_top
435 */
435 */
436 Notebook.prototype.scroll_to_top = function () {
436 Notebook.prototype.scroll_to_top = function () {
437 this.element.animate({scrollTop:0}, 0);
437 this.element.animate({scrollTop:0}, 0);
438 };
438 };
439
439
440
440
441 // Cell indexing, retrieval, etc.
441 // Cell indexing, retrieval, etc.
442
442
443 /**
443 /**
444 * Get all cell elements in the notebook.
444 * Get all cell elements in the notebook.
445 *
445 *
446 * @method get_cell_elements
446 * @method get_cell_elements
447 * @return {jQuery} A selector of all cell elements
447 * @return {jQuery} A selector of all cell elements
448 */
448 */
449 Notebook.prototype.get_cell_elements = function () {
449 Notebook.prototype.get_cell_elements = function () {
450 return this.container.children("div.cell");
450 return this.container.children("div.cell");
451 };
451 };
452
452
453 /**
453 /**
454 * Get a particular cell element.
454 * Get a particular cell element.
455 *
455 *
456 * @method get_cell_element
456 * @method get_cell_element
457 * @param {Number} index An index of a cell to select
457 * @param {Number} index An index of a cell to select
458 * @return {jQuery} A selector of the given cell.
458 * @return {jQuery} A selector of the given cell.
459 */
459 */
460 Notebook.prototype.get_cell_element = function (index) {
460 Notebook.prototype.get_cell_element = function (index) {
461 var result = null;
461 var result = null;
462 var e = this.get_cell_elements().eq(index);
462 var e = this.get_cell_elements().eq(index);
463 if (e.length !== 0) {
463 if (e.length !== 0) {
464 result = e;
464 result = e;
465 }
465 }
466 return result;
466 return result;
467 };
467 };
468
468
469 /**
469 /**
470 * Count the cells in this notebook.
470 * Count the cells in this notebook.
471 *
471 *
472 * @method ncells
472 * @method ncells
473 * @return {Number} The number of cells in this notebook
473 * @return {Number} The number of cells in this notebook
474 */
474 */
475 Notebook.prototype.ncells = function () {
475 Notebook.prototype.ncells = function () {
476 return this.get_cell_elements().length;
476 return this.get_cell_elements().length;
477 };
477 };
478
478
479 /**
479 /**
480 * Get all Cell objects in this notebook.
480 * Get all Cell objects in this notebook.
481 *
481 *
482 * @method get_cells
482 * @method get_cells
483 * @return {Array} This notebook's Cell objects
483 * @return {Array} This notebook's Cell objects
484 */
484 */
485 // TODO: we are often calling cells as cells()[i], which we should optimize
485 // TODO: we are often calling cells as cells()[i], which we should optimize
486 // to cells(i) or a new method.
486 // to cells(i) or a new method.
487 Notebook.prototype.get_cells = function () {
487 Notebook.prototype.get_cells = function () {
488 return this.get_cell_elements().toArray().map(function (e) {
488 return this.get_cell_elements().toArray().map(function (e) {
489 return $(e).data("cell");
489 return $(e).data("cell");
490 });
490 });
491 };
491 };
492
492
493 /**
493 /**
494 * Get a Cell object from this notebook.
494 * Get a Cell object from this notebook.
495 *
495 *
496 * @method get_cell
496 * @method get_cell
497 * @param {Number} index An index of a cell to retrieve
497 * @param {Number} index An index of a cell to retrieve
498 * @return {Cell} A particular cell
498 * @return {Cell} A particular cell
499 */
499 */
500 Notebook.prototype.get_cell = function (index) {
500 Notebook.prototype.get_cell = function (index) {
501 var result = null;
501 var result = null;
502 var ce = this.get_cell_element(index);
502 var ce = this.get_cell_element(index);
503 if (ce !== null) {
503 if (ce !== null) {
504 result = ce.data('cell');
504 result = ce.data('cell');
505 }
505 }
506 return result;
506 return result;
507 }
507 }
508
508
509 /**
509 /**
510 * Get the cell below a given cell.
510 * Get the cell below a given cell.
511 *
511 *
512 * @method get_next_cell
512 * @method get_next_cell
513 * @param {Cell} cell The provided cell
513 * @param {Cell} cell The provided cell
514 * @return {Cell} The next cell
514 * @return {Cell} The next cell
515 */
515 */
516 Notebook.prototype.get_next_cell = function (cell) {
516 Notebook.prototype.get_next_cell = function (cell) {
517 var result = null;
517 var result = null;
518 var index = this.find_cell_index(cell);
518 var index = this.find_cell_index(cell);
519 if (this.is_valid_cell_index(index+1)) {
519 if (this.is_valid_cell_index(index+1)) {
520 result = this.get_cell(index+1);
520 result = this.get_cell(index+1);
521 }
521 }
522 return result;
522 return result;
523 }
523 }
524
524
525 /**
525 /**
526 * Get the cell above a given cell.
526 * Get the cell above a given cell.
527 *
527 *
528 * @method get_prev_cell
528 * @method get_prev_cell
529 * @param {Cell} cell The provided cell
529 * @param {Cell} cell The provided cell
530 * @return {Cell} The previous cell
530 * @return {Cell} The previous cell
531 */
531 */
532 Notebook.prototype.get_prev_cell = function (cell) {
532 Notebook.prototype.get_prev_cell = function (cell) {
533 // TODO: off-by-one
533 // TODO: off-by-one
534 // nb.get_prev_cell(nb.get_cell(1)) is null
534 // nb.get_prev_cell(nb.get_cell(1)) is null
535 var result = null;
535 var result = null;
536 var index = this.find_cell_index(cell);
536 var index = this.find_cell_index(cell);
537 if (index !== null && index > 1) {
537 if (index !== null && index > 1) {
538 result = this.get_cell(index-1);
538 result = this.get_cell(index-1);
539 }
539 }
540 return result;
540 return result;
541 }
541 }
542
542
543 /**
543 /**
544 * Get the numeric index of a given cell.
544 * Get the numeric index of a given cell.
545 *
545 *
546 * @method find_cell_index
546 * @method find_cell_index
547 * @param {Cell} cell The provided cell
547 * @param {Cell} cell The provided cell
548 * @return {Number} The cell's numeric index
548 * @return {Number} The cell's numeric index
549 */
549 */
550 Notebook.prototype.find_cell_index = function (cell) {
550 Notebook.prototype.find_cell_index = function (cell) {
551 var result = null;
551 var result = null;
552 this.get_cell_elements().filter(function (index) {
552 this.get_cell_elements().filter(function (index) {
553 if ($(this).data("cell") === cell) {
553 if ($(this).data("cell") === cell) {
554 result = index;
554 result = index;
555 };
555 };
556 });
556 });
557 return result;
557 return result;
558 };
558 };
559
559
560 /**
560 /**
561 * Get a given index , or the selected index if none is provided.
561 * Get a given index , or the selected index if none is provided.
562 *
562 *
563 * @method index_or_selected
563 * @method index_or_selected
564 * @param {Number} index A cell's index
564 * @param {Number} index A cell's index
565 * @return {Number} The given index, or selected index if none is provided.
565 * @return {Number} The given index, or selected index if none is provided.
566 */
566 */
567 Notebook.prototype.index_or_selected = function (index) {
567 Notebook.prototype.index_or_selected = function (index) {
568 var i;
568 var i;
569 if (index === undefined || index === null) {
569 if (index === undefined || index === null) {
570 i = this.get_selected_index();
570 i = this.get_selected_index();
571 if (i === null) {
571 if (i === null) {
572 i = 0;
572 i = 0;
573 }
573 }
574 } else {
574 } else {
575 i = index;
575 i = index;
576 }
576 }
577 return i;
577 return i;
578 };
578 };
579
579
580 /**
580 /**
581 * Get the currently selected cell.
581 * Get the currently selected cell.
582 * @method get_selected_cell
582 * @method get_selected_cell
583 * @return {Cell} The selected cell
583 * @return {Cell} The selected cell
584 */
584 */
585 Notebook.prototype.get_selected_cell = function () {
585 Notebook.prototype.get_selected_cell = function () {
586 var index = this.get_selected_index();
586 var index = this.get_selected_index();
587 return this.get_cell(index);
587 return this.get_cell(index);
588 };
588 };
589
589
590 /**
590 /**
591 * Check whether a cell index is valid.
591 * Check whether a cell index is valid.
592 *
592 *
593 * @method is_valid_cell_index
593 * @method is_valid_cell_index
594 * @param {Number} index A cell index
594 * @param {Number} index A cell index
595 * @return True if the index is valid, false otherwise
595 * @return True if the index is valid, false otherwise
596 */
596 */
597 Notebook.prototype.is_valid_cell_index = function (index) {
597 Notebook.prototype.is_valid_cell_index = function (index) {
598 if (index !== null && index >= 0 && index < this.ncells()) {
598 if (index !== null && index >= 0 && index < this.ncells()) {
599 return true;
599 return true;
600 } else {
600 } else {
601 return false;
601 return false;
602 };
602 };
603 }
603 }
604
604
605 /**
605 /**
606 * Get the index of the currently selected cell.
606 * Get the index of the currently selected cell.
607
607
608 * @method get_selected_index
608 * @method get_selected_index
609 * @return {Number} The selected cell's numeric index
609 * @return {Number} The selected cell's numeric index
610 */
610 */
611 Notebook.prototype.get_selected_index = function () {
611 Notebook.prototype.get_selected_index = function () {
612 var result = null;
612 var result = null;
613 this.get_cell_elements().filter(function (index) {
613 this.get_cell_elements().filter(function (index) {
614 if ($(this).data("cell").selected === true) {
614 if ($(this).data("cell").selected === true) {
615 result = index;
615 result = index;
616 };
616 };
617 });
617 });
618 return result;
618 return result;
619 };
619 };
620
620
621
621
622 // Cell selection.
622 // Cell selection.
623
623
624 /**
624 /**
625 * Programmatically select a cell.
625 * Programmatically select a cell.
626 *
626 *
627 * @method select
627 * @method select
628 * @param {Number} index A cell's index
628 * @param {Number} index A cell's index
629 * @return {Notebook} This notebook
629 * @return {Notebook} This notebook
630 */
630 */
631 Notebook.prototype.select = function (index) {
631 Notebook.prototype.select = function (index) {
632 if (this.is_valid_cell_index(index)) {
632 if (this.is_valid_cell_index(index)) {
633 var sindex = this.get_selected_index()
633 var sindex = this.get_selected_index()
634 if (sindex !== null && index !== sindex) {
634 if (sindex !== null && index !== sindex) {
635 this.get_cell(sindex).unselect();
635 this.get_cell(sindex).unselect();
636 };
636 };
637 var cell = this.get_cell(index);
637 var cell = this.get_cell(index);
638 cell.select();
638 cell.select();
639 if (cell.cell_type === 'heading') {
639 if (cell.cell_type === 'heading') {
640 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
640 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
641 {'cell_type':cell.cell_type,level:cell.level}
641 {'cell_type':cell.cell_type,level:cell.level}
642 );
642 );
643 } else {
643 } else {
644 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
644 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
645 {'cell_type':cell.cell_type}
645 {'cell_type':cell.cell_type}
646 );
646 );
647 };
647 };
648 };
648 };
649 return this;
649 return this;
650 };
650 };
651
651
652 /**
652 /**
653 * Programmatically select the next cell.
653 * Programmatically select the next cell.
654 *
654 *
655 * @method select_next
655 * @method select_next
656 * @return {Notebook} This notebook
656 * @return {Notebook} This notebook
657 */
657 */
658 Notebook.prototype.select_next = function () {
658 Notebook.prototype.select_next = function () {
659 var index = this.get_selected_index();
659 var index = this.get_selected_index();
660 this.select(index+1);
660 this.select(index+1);
661 return this;
661 return this;
662 };
662 };
663
663
664 /**
664 /**
665 * Programmatically select the previous cell.
665 * Programmatically select the previous cell.
666 *
666 *
667 * @method select_prev
667 * @method select_prev
668 * @return {Notebook} This notebook
668 * @return {Notebook} This notebook
669 */
669 */
670 Notebook.prototype.select_prev = function () {
670 Notebook.prototype.select_prev = function () {
671 var index = this.get_selected_index();
671 var index = this.get_selected_index();
672 this.select(index-1);
672 this.select(index-1);
673 return this;
673 return this;
674 };
674 };
675
675
676
676
677 // Cell movement
677 // Cell movement
678
678
679 /**
679 /**
680 * Move given (or selected) cell up and select it.
680 * Move given (or selected) cell up and select it.
681 *
681 *
682 * @method move_cell_up
682 * @method move_cell_up
683 * @param [index] {integer} cell index
683 * @param [index] {integer} cell index
684 * @return {Notebook} This notebook
684 * @return {Notebook} This notebook
685 **/
685 **/
686 Notebook.prototype.move_cell_up = function (index) {
686 Notebook.prototype.move_cell_up = function (index) {
687 var i = this.index_or_selected(index);
687 var i = this.index_or_selected(index);
688 if (this.is_valid_cell_index(i) && i > 0) {
688 if (this.is_valid_cell_index(i) && i > 0) {
689 var pivot = this.get_cell_element(i-1);
689 var pivot = this.get_cell_element(i-1);
690 var tomove = this.get_cell_element(i);
690 var tomove = this.get_cell_element(i);
691 if (pivot !== null && tomove !== null) {
691 if (pivot !== null && tomove !== null) {
692 tomove.detach();
692 tomove.detach();
693 pivot.before(tomove);
693 pivot.before(tomove);
694 this.select(i-1);
694 this.select(i-1);
695 };
695 };
696 this.set_dirty(true);
696 this.set_dirty(true);
697 };
697 };
698 return this;
698 return this;
699 };
699 };
700
700
701
701
702 /**
702 /**
703 * Move given (or selected) cell down and select it
703 * Move given (or selected) cell down and select it
704 *
704 *
705 * @method move_cell_down
705 * @method move_cell_down
706 * @param [index] {integer} cell index
706 * @param [index] {integer} cell index
707 * @return {Notebook} This notebook
707 * @return {Notebook} This notebook
708 **/
708 **/
709 Notebook.prototype.move_cell_down = function (index) {
709 Notebook.prototype.move_cell_down = function (index) {
710 var i = this.index_or_selected(index);
710 var i = this.index_or_selected(index);
711 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
711 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
712 var pivot = this.get_cell_element(i+1);
712 var pivot = this.get_cell_element(i+1);
713 var tomove = this.get_cell_element(i);
713 var tomove = this.get_cell_element(i);
714 if (pivot !== null && tomove !== null) {
714 if (pivot !== null && tomove !== null) {
715 tomove.detach();
715 tomove.detach();
716 pivot.after(tomove);
716 pivot.after(tomove);
717 this.select(i+1);
717 this.select(i+1);
718 };
718 };
719 };
719 };
720 this.set_dirty();
720 this.set_dirty();
721 return this;
721 return this;
722 };
722 };
723
723
724
724
725 // Insertion, deletion.
725 // Insertion, deletion.
726
726
727 /**
727 /**
728 * Delete a cell from the notebook.
728 * Delete a cell from the notebook.
729 *
729 *
730 * @method delete_cell
730 * @method delete_cell
731 * @param [index] A cell's numeric index
731 * @param [index] A cell's numeric index
732 * @return {Notebook} This notebook
732 * @return {Notebook} This notebook
733 */
733 */
734 Notebook.prototype.delete_cell = function (index) {
734 Notebook.prototype.delete_cell = function (index) {
735 var i = this.index_or_selected(index);
735 var i = this.index_or_selected(index);
736 var cell = this.get_selected_cell();
736 var cell = this.get_selected_cell();
737 this.undelete_backup = cell.toJSON();
737 this.undelete_backup = cell.toJSON();
738 $('#undelete_cell').removeClass('disabled');
738 $('#undelete_cell').removeClass('disabled');
739 if (this.is_valid_cell_index(i)) {
739 if (this.is_valid_cell_index(i)) {
740 var ce = this.get_cell_element(i);
740 var ce = this.get_cell_element(i);
741 ce.remove();
741 ce.remove();
742 if (i === (this.ncells())) {
742 if (i === (this.ncells())) {
743 this.select(i-1);
743 this.select(i-1);
744 this.undelete_index = i - 1;
744 this.undelete_index = i - 1;
745 this.undelete_below = true;
745 this.undelete_below = true;
746 } else {
746 } else {
747 this.select(i);
747 this.select(i);
748 this.undelete_index = i;
748 this.undelete_index = i;
749 this.undelete_below = false;
749 this.undelete_below = false;
750 };
750 };
751 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
751 $([IPython.events]).trigger('delete.Cell', {'cell': cell, 'index': i});
752 this.set_dirty(true);
752 this.set_dirty(true);
753 };
753 };
754 return this;
754 return this;
755 };
755 };
756
756
757 /**
757 /**
758 * Insert a cell so that after insertion the cell is at given index.
758 * Insert a cell so that after insertion the cell is at given index.
759 *
759 *
760 * Similar to insert_above, but index parameter is mandatory
760 * Similar to insert_above, but index parameter is mandatory
761 *
761 *
762 * Index will be brought back into the accissible range [0,n]
762 * Index will be brought back into the accissible range [0,n]
763 *
763 *
764 * @method insert_cell_at_index
764 * @method insert_cell_at_index
765 * @param type {string} in ['code','markdown','heading']
765 * @param type {string} in ['code','markdown','heading']
766 * @param [index] {int} a valid index where to inser cell
766 * @param [index] {int} a valid index where to inser cell
767 *
767 *
768 * @return cell {cell|null} created cell or null
768 * @return cell {cell|null} created cell or null
769 **/
769 **/
770 Notebook.prototype.insert_cell_at_index = function(type, index){
770 Notebook.prototype.insert_cell_at_index = function(type, index){
771
771
772 var ncells = this.ncells();
772 var ncells = this.ncells();
773 var index = Math.min(index,ncells);
773 var index = Math.min(index,ncells);
774 index = Math.max(index,0);
774 index = Math.max(index,0);
775 var cell = null;
775 var cell = null;
776
776
777 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
777 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
778 if (type === 'code') {
778 if (type === 'code') {
779 cell = new IPython.CodeCell(this.kernel);
779 cell = new IPython.CodeCell(this.kernel);
780 cell.set_input_prompt();
780 cell.set_input_prompt();
781 } else if (type === 'markdown') {
781 } else if (type === 'markdown') {
782 cell = new IPython.MarkdownCell();
782 cell = new IPython.MarkdownCell();
783 } else if (type === 'raw') {
783 } else if (type === 'raw') {
784 cell = new IPython.RawCell();
784 cell = new IPython.RawCell();
785 } else if (type === 'heading') {
785 } else if (type === 'heading') {
786 cell = new IPython.HeadingCell();
786 cell = new IPython.HeadingCell();
787 }
787 }
788
788
789 if(this._insert_element_at_index(cell.element,index)){
789 if(this._insert_element_at_index(cell.element,index)){
790 cell.render();
790 cell.render();
791 this.select(this.find_cell_index(cell));
791 this.select(this.find_cell_index(cell));
792 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
792 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
793 this.set_dirty(true);
793 this.set_dirty(true);
794 }
794 }
795 }
795 }
796 return cell;
796 return cell;
797
797
798 };
798 };
799
799
800 /**
800 /**
801 * Insert an element at given cell index.
801 * Insert an element at given cell index.
802 *
802 *
803 * @method _insert_element_at_index
803 * @method _insert_element_at_index
804 * @param element {dom element} a cell element
804 * @param element {dom element} a cell element
805 * @param [index] {int} a valid index where to inser cell
805 * @param [index] {int} a valid index where to inser cell
806 * @private
806 * @private
807 *
807 *
808 * return true if everything whent fine.
808 * return true if everything whent fine.
809 **/
809 **/
810 Notebook.prototype._insert_element_at_index = function(element, index){
810 Notebook.prototype._insert_element_at_index = function(element, index){
811 if (element === undefined){
811 if (element === undefined){
812 return false;
812 return false;
813 }
813 }
814
814
815 var ncells = this.ncells();
815 var ncells = this.ncells();
816
816
817 if (ncells === 0) {
817 if (ncells === 0) {
818 // special case append if empty
818 // special case append if empty
819 this.element.find('div.end_space').before(element);
819 this.element.find('div.end_space').before(element);
820 } else if ( ncells === index ) {
820 } else if ( ncells === index ) {
821 // special case append it the end, but not empty
821 // special case append it the end, but not empty
822 this.get_cell_element(index-1).after(element);
822 this.get_cell_element(index-1).after(element);
823 } else if (this.is_valid_cell_index(index)) {
823 } else if (this.is_valid_cell_index(index)) {
824 // otherwise always somewhere to append to
824 // otherwise always somewhere to append to
825 this.get_cell_element(index).before(element);
825 this.get_cell_element(index).before(element);
826 } else {
826 } else {
827 return false;
827 return false;
828 }
828 }
829
829
830 if (this.undelete_index !== null && index <= this.undelete_index) {
830 if (this.undelete_index !== null && index <= this.undelete_index) {
831 this.undelete_index = this.undelete_index + 1;
831 this.undelete_index = this.undelete_index + 1;
832 this.set_dirty(true);
832 this.set_dirty(true);
833 }
833 }
834 return true;
834 return true;
835 };
835 };
836
836
837 /**
837 /**
838 * Insert a cell of given type above given index, or at top
838 * Insert a cell of given type above given index, or at top
839 * of notebook if index smaller than 0.
839 * of notebook if index smaller than 0.
840 *
840 *
841 * default index value is the one of currently selected cell
841 * default index value is the one of currently selected cell
842 *
842 *
843 * @method insert_cell_above
843 * @method insert_cell_above
844 * @param type {string} cell type
844 * @param type {string} cell type
845 * @param [index] {integer}
845 * @param [index] {integer}
846 *
846 *
847 * @return handle to created cell or null
847 * @return handle to created cell or null
848 **/
848 **/
849 Notebook.prototype.insert_cell_above = function (type, index) {
849 Notebook.prototype.insert_cell_above = function (type, index) {
850 index = this.index_or_selected(index);
850 index = this.index_or_selected(index);
851 return this.insert_cell_at_index(type, index);
851 return this.insert_cell_at_index(type, index);
852 };
852 };
853
853
854 /**
854 /**
855 * Insert a cell of given type below given index, or at bottom
855 * Insert a cell of given type below given index, or at bottom
856 * of notebook if index greater thatn number of cell
856 * of notebook if index greater thatn number of cell
857 *
857 *
858 * default index value is the one of currently selected cell
858 * default index value is the one of currently selected cell
859 *
859 *
860 * @method insert_cell_below
860 * @method insert_cell_below
861 * @param type {string} cell type
861 * @param type {string} cell type
862 * @param [index] {integer}
862 * @param [index] {integer}
863 *
863 *
864 * @return handle to created cell or null
864 * @return handle to created cell or null
865 *
865 *
866 **/
866 **/
867 Notebook.prototype.insert_cell_below = function (type, index) {
867 Notebook.prototype.insert_cell_below = function (type, index) {
868 index = this.index_or_selected(index);
868 index = this.index_or_selected(index);
869 return this.insert_cell_at_index(type, index+1);
869 return this.insert_cell_at_index(type, index+1);
870 };
870 };
871
871
872
872
873 /**
873 /**
874 * Insert cell at end of notebook
874 * Insert cell at end of notebook
875 *
875 *
876 * @method insert_cell_at_bottom
876 * @method insert_cell_at_bottom
877 * @param {String} type cell type
877 * @param {String} type cell type
878 *
878 *
879 * @return the added cell; or null
879 * @return the added cell; or null
880 **/
880 **/
881 Notebook.prototype.insert_cell_at_bottom = function (type){
881 Notebook.prototype.insert_cell_at_bottom = function (type){
882 var len = this.ncells();
882 var len = this.ncells();
883 return this.insert_cell_below(type,len-1);
883 return this.insert_cell_below(type,len-1);
884 };
884 };
885
885
886 /**
886 /**
887 * Turn a cell into a code cell.
887 * Turn a cell into a code cell.
888 *
888 *
889 * @method to_code
889 * @method to_code
890 * @param {Number} [index] A cell's index
890 * @param {Number} [index] A cell's index
891 */
891 */
892 Notebook.prototype.to_code = function (index) {
892 Notebook.prototype.to_code = function (index) {
893 var i = this.index_or_selected(index);
893 var i = this.index_or_selected(index);
894 if (this.is_valid_cell_index(i)) {
894 if (this.is_valid_cell_index(i)) {
895 var source_element = this.get_cell_element(i);
895 var source_element = this.get_cell_element(i);
896 var source_cell = source_element.data("cell");
896 var source_cell = source_element.data("cell");
897 if (!(source_cell instanceof IPython.CodeCell)) {
897 if (!(source_cell instanceof IPython.CodeCell)) {
898 var target_cell = this.insert_cell_below('code',i);
898 var target_cell = this.insert_cell_below('code',i);
899 var text = source_cell.get_text();
899 var text = source_cell.get_text();
900 if (text === source_cell.placeholder) {
900 if (text === source_cell.placeholder) {
901 text = '';
901 text = '';
902 }
902 }
903 target_cell.set_text(text);
903 target_cell.set_text(text);
904 // make this value the starting point, so that we can only undo
904 // make this value the starting point, so that we can only undo
905 // to this state, instead of a blank cell
905 // to this state, instead of a blank cell
906 target_cell.code_mirror.clearHistory();
906 target_cell.code_mirror.clearHistory();
907 source_element.remove();
907 source_element.remove();
908 this.set_dirty(true);
908 this.set_dirty(true);
909 };
909 };
910 };
910 };
911 };
911 };
912
912
913 /**
913 /**
914 * Turn a cell into a Markdown cell.
914 * Turn a cell into a Markdown cell.
915 *
915 *
916 * @method to_markdown
916 * @method to_markdown
917 * @param {Number} [index] A cell's index
917 * @param {Number} [index] A cell's index
918 */
918 */
919 Notebook.prototype.to_markdown = function (index) {
919 Notebook.prototype.to_markdown = function (index) {
920 var i = this.index_or_selected(index);
920 var i = this.index_or_selected(index);
921 if (this.is_valid_cell_index(i)) {
921 if (this.is_valid_cell_index(i)) {
922 var source_element = this.get_cell_element(i);
922 var source_element = this.get_cell_element(i);
923 var source_cell = source_element.data("cell");
923 var source_cell = source_element.data("cell");
924 if (!(source_cell instanceof IPython.MarkdownCell)) {
924 if (!(source_cell instanceof IPython.MarkdownCell)) {
925 var target_cell = this.insert_cell_below('markdown',i);
925 var target_cell = this.insert_cell_below('markdown',i);
926 var text = source_cell.get_text();
926 var text = source_cell.get_text();
927 if (text === source_cell.placeholder) {
927 if (text === source_cell.placeholder) {
928 text = '';
928 text = '';
929 };
929 };
930 // The edit must come before the set_text.
930 // The edit must come before the set_text.
931 target_cell.edit();
931 target_cell.edit();
932 target_cell.set_text(text);
932 target_cell.set_text(text);
933 // make this value the starting point, so that we can only undo
933 // make this value the starting point, so that we can only undo
934 // to this state, instead of a blank cell
934 // to this state, instead of a blank cell
935 target_cell.code_mirror.clearHistory();
935 target_cell.code_mirror.clearHistory();
936 source_element.remove();
936 source_element.remove();
937 this.set_dirty(true);
937 this.set_dirty(true);
938 };
938 };
939 };
939 };
940 };
940 };
941
941
942 /**
942 /**
943 * Turn a cell into a raw text cell.
943 * Turn a cell into a raw text cell.
944 *
944 *
945 * @method to_raw
945 * @method to_raw
946 * @param {Number} [index] A cell's index
946 * @param {Number} [index] A cell's index
947 */
947 */
948 Notebook.prototype.to_raw = function (index) {
948 Notebook.prototype.to_raw = function (index) {
949 var i = this.index_or_selected(index);
949 var i = this.index_or_selected(index);
950 if (this.is_valid_cell_index(i)) {
950 if (this.is_valid_cell_index(i)) {
951 var source_element = this.get_cell_element(i);
951 var source_element = this.get_cell_element(i);
952 var source_cell = source_element.data("cell");
952 var source_cell = source_element.data("cell");
953 var target_cell = null;
953 var target_cell = null;
954 if (!(source_cell instanceof IPython.RawCell)) {
954 if (!(source_cell instanceof IPython.RawCell)) {
955 target_cell = this.insert_cell_below('raw',i);
955 target_cell = this.insert_cell_below('raw',i);
956 var text = source_cell.get_text();
956 var text = source_cell.get_text();
957 if (text === source_cell.placeholder) {
957 if (text === source_cell.placeholder) {
958 text = '';
958 text = '';
959 };
959 };
960 // The edit must come before the set_text.
960 // The edit must come before the set_text.
961 target_cell.edit();
961 target_cell.edit();
962 target_cell.set_text(text);
962 target_cell.set_text(text);
963 // make this value the starting point, so that we can only undo
963 // make this value the starting point, so that we can only undo
964 // to this state, instead of a blank cell
964 // to this state, instead of a blank cell
965 target_cell.code_mirror.clearHistory();
965 target_cell.code_mirror.clearHistory();
966 source_element.remove();
966 source_element.remove();
967 this.set_dirty(true);
967 this.set_dirty(true);
968 };
968 };
969 };
969 };
970 };
970 };
971
971
972 /**
972 /**
973 * Turn a cell into a heading cell.
973 * Turn a cell into a heading cell.
974 *
974 *
975 * @method to_heading
975 * @method to_heading
976 * @param {Number} [index] A cell's index
976 * @param {Number} [index] A cell's index
977 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
977 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
978 */
978 */
979 Notebook.prototype.to_heading = function (index, level) {
979 Notebook.prototype.to_heading = function (index, level) {
980 level = level || 1;
980 level = level || 1;
981 var i = this.index_or_selected(index);
981 var i = this.index_or_selected(index);
982 if (this.is_valid_cell_index(i)) {
982 if (this.is_valid_cell_index(i)) {
983 var source_element = this.get_cell_element(i);
983 var source_element = this.get_cell_element(i);
984 var source_cell = source_element.data("cell");
984 var source_cell = source_element.data("cell");
985 var target_cell = null;
985 var target_cell = null;
986 if (source_cell instanceof IPython.HeadingCell) {
986 if (source_cell instanceof IPython.HeadingCell) {
987 source_cell.set_level(level);
987 source_cell.set_level(level);
988 } else {
988 } else {
989 target_cell = this.insert_cell_below('heading',i);
989 target_cell = this.insert_cell_below('heading',i);
990 var text = source_cell.get_text();
990 var text = source_cell.get_text();
991 if (text === source_cell.placeholder) {
991 if (text === source_cell.placeholder) {
992 text = '';
992 text = '';
993 };
993 };
994 // The edit must come before the set_text.
994 // The edit must come before the set_text.
995 target_cell.set_level(level);
995 target_cell.set_level(level);
996 target_cell.edit();
996 target_cell.edit();
997 target_cell.set_text(text);
997 target_cell.set_text(text);
998 // make this value the starting point, so that we can only undo
998 // make this value the starting point, so that we can only undo
999 // to this state, instead of a blank cell
999 // to this state, instead of a blank cell
1000 target_cell.code_mirror.clearHistory();
1000 target_cell.code_mirror.clearHistory();
1001 source_element.remove();
1001 source_element.remove();
1002 this.set_dirty(true);
1002 this.set_dirty(true);
1003 };
1003 };
1004 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1004 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1005 {'cell_type':'heading',level:level}
1005 {'cell_type':'heading',level:level}
1006 );
1006 );
1007 };
1007 };
1008 };
1008 };
1009
1009
1010
1010
1011 // Cut/Copy/Paste
1011 // Cut/Copy/Paste
1012
1012
1013 /**
1013 /**
1014 * Enable UI elements for pasting cells.
1014 * Enable UI elements for pasting cells.
1015 *
1015 *
1016 * @method enable_paste
1016 * @method enable_paste
1017 */
1017 */
1018 Notebook.prototype.enable_paste = function () {
1018 Notebook.prototype.enable_paste = function () {
1019 var that = this;
1019 var that = this;
1020 if (!this.paste_enabled) {
1020 if (!this.paste_enabled) {
1021 $('#paste_cell_replace').removeClass('disabled')
1021 $('#paste_cell_replace').removeClass('disabled')
1022 .on('click', function () {that.paste_cell_replace();});
1022 .on('click', function () {that.paste_cell_replace();});
1023 $('#paste_cell_above').removeClass('disabled')
1023 $('#paste_cell_above').removeClass('disabled')
1024 .on('click', function () {that.paste_cell_above();});
1024 .on('click', function () {that.paste_cell_above();});
1025 $('#paste_cell_below').removeClass('disabled')
1025 $('#paste_cell_below').removeClass('disabled')
1026 .on('click', function () {that.paste_cell_below();});
1026 .on('click', function () {that.paste_cell_below();});
1027 this.paste_enabled = true;
1027 this.paste_enabled = true;
1028 };
1028 };
1029 };
1029 };
1030
1030
1031 /**
1031 /**
1032 * Disable UI elements for pasting cells.
1032 * Disable UI elements for pasting cells.
1033 *
1033 *
1034 * @method disable_paste
1034 * @method disable_paste
1035 */
1035 */
1036 Notebook.prototype.disable_paste = function () {
1036 Notebook.prototype.disable_paste = function () {
1037 if (this.paste_enabled) {
1037 if (this.paste_enabled) {
1038 $('#paste_cell_replace').addClass('disabled').off('click');
1038 $('#paste_cell_replace').addClass('disabled').off('click');
1039 $('#paste_cell_above').addClass('disabled').off('click');
1039 $('#paste_cell_above').addClass('disabled').off('click');
1040 $('#paste_cell_below').addClass('disabled').off('click');
1040 $('#paste_cell_below').addClass('disabled').off('click');
1041 this.paste_enabled = false;
1041 this.paste_enabled = false;
1042 };
1042 };
1043 };
1043 };
1044
1044
1045 /**
1045 /**
1046 * Cut a cell.
1046 * Cut a cell.
1047 *
1047 *
1048 * @method cut_cell
1048 * @method cut_cell
1049 */
1049 */
1050 Notebook.prototype.cut_cell = function () {
1050 Notebook.prototype.cut_cell = function () {
1051 this.copy_cell();
1051 this.copy_cell();
1052 this.delete_cell();
1052 this.delete_cell();
1053 }
1053 }
1054
1054
1055 /**
1055 /**
1056 * Copy a cell.
1056 * Copy a cell.
1057 *
1057 *
1058 * @method copy_cell
1058 * @method copy_cell
1059 */
1059 */
1060 Notebook.prototype.copy_cell = function () {
1060 Notebook.prototype.copy_cell = function () {
1061 var cell = this.get_selected_cell();
1061 var cell = this.get_selected_cell();
1062 this.clipboard = cell.toJSON();
1062 this.clipboard = cell.toJSON();
1063 this.enable_paste();
1063 this.enable_paste();
1064 };
1064 };
1065
1065
1066 /**
1066 /**
1067 * Replace the selected cell with a cell in the clipboard.
1067 * Replace the selected cell with a cell in the clipboard.
1068 *
1068 *
1069 * @method paste_cell_replace
1069 * @method paste_cell_replace
1070 */
1070 */
1071 Notebook.prototype.paste_cell_replace = function () {
1071 Notebook.prototype.paste_cell_replace = function () {
1072 if (this.clipboard !== null && this.paste_enabled) {
1072 if (this.clipboard !== null && this.paste_enabled) {
1073 var cell_data = this.clipboard;
1073 var cell_data = this.clipboard;
1074 var new_cell = this.insert_cell_above(cell_data.cell_type);
1074 var new_cell = this.insert_cell_above(cell_data.cell_type);
1075 new_cell.fromJSON(cell_data);
1075 new_cell.fromJSON(cell_data);
1076 var old_cell = this.get_next_cell(new_cell);
1076 var old_cell = this.get_next_cell(new_cell);
1077 this.delete_cell(this.find_cell_index(old_cell));
1077 this.delete_cell(this.find_cell_index(old_cell));
1078 this.select(this.find_cell_index(new_cell));
1078 this.select(this.find_cell_index(new_cell));
1079 };
1079 };
1080 };
1080 };
1081
1081
1082 /**
1082 /**
1083 * Paste a cell from the clipboard above the selected cell.
1083 * Paste a cell from the clipboard above the selected cell.
1084 *
1084 *
1085 * @method paste_cell_above
1085 * @method paste_cell_above
1086 */
1086 */
1087 Notebook.prototype.paste_cell_above = function () {
1087 Notebook.prototype.paste_cell_above = function () {
1088 if (this.clipboard !== null && this.paste_enabled) {
1088 if (this.clipboard !== null && this.paste_enabled) {
1089 var cell_data = this.clipboard;
1089 var cell_data = this.clipboard;
1090 var new_cell = this.insert_cell_above(cell_data.cell_type);
1090 var new_cell = this.insert_cell_above(cell_data.cell_type);
1091 new_cell.fromJSON(cell_data);
1091 new_cell.fromJSON(cell_data);
1092 };
1092 };
1093 };
1093 };
1094
1094
1095 /**
1095 /**
1096 * Paste a cell from the clipboard below the selected cell.
1096 * Paste a cell from the clipboard below the selected cell.
1097 *
1097 *
1098 * @method paste_cell_below
1098 * @method paste_cell_below
1099 */
1099 */
1100 Notebook.prototype.paste_cell_below = function () {
1100 Notebook.prototype.paste_cell_below = function () {
1101 if (this.clipboard !== null && this.paste_enabled) {
1101 if (this.clipboard !== null && this.paste_enabled) {
1102 var cell_data = this.clipboard;
1102 var cell_data = this.clipboard;
1103 var new_cell = this.insert_cell_below(cell_data.cell_type);
1103 var new_cell = this.insert_cell_below(cell_data.cell_type);
1104 new_cell.fromJSON(cell_data);
1104 new_cell.fromJSON(cell_data);
1105 };
1105 };
1106 };
1106 };
1107
1107
1108 // Cell undelete
1108 // Cell undelete
1109
1109
1110 /**
1110 /**
1111 * Restore the most recently deleted cell.
1111 * Restore the most recently deleted cell.
1112 *
1112 *
1113 * @method undelete
1113 * @method undelete
1114 */
1114 */
1115 Notebook.prototype.undelete = function() {
1115 Notebook.prototype.undelete = function() {
1116 if (this.undelete_backup !== null && this.undelete_index !== null) {
1116 if (this.undelete_backup !== null && this.undelete_index !== null) {
1117 var current_index = this.get_selected_index();
1117 var current_index = this.get_selected_index();
1118 if (this.undelete_index < current_index) {
1118 if (this.undelete_index < current_index) {
1119 current_index = current_index + 1;
1119 current_index = current_index + 1;
1120 }
1120 }
1121 if (this.undelete_index >= this.ncells()) {
1121 if (this.undelete_index >= this.ncells()) {
1122 this.select(this.ncells() - 1);
1122 this.select(this.ncells() - 1);
1123 }
1123 }
1124 else {
1124 else {
1125 this.select(this.undelete_index);
1125 this.select(this.undelete_index);
1126 }
1126 }
1127 var cell_data = this.undelete_backup;
1127 var cell_data = this.undelete_backup;
1128 var new_cell = null;
1128 var new_cell = null;
1129 if (this.undelete_below) {
1129 if (this.undelete_below) {
1130 new_cell = this.insert_cell_below(cell_data.cell_type);
1130 new_cell = this.insert_cell_below(cell_data.cell_type);
1131 } else {
1131 } else {
1132 new_cell = this.insert_cell_above(cell_data.cell_type);
1132 new_cell = this.insert_cell_above(cell_data.cell_type);
1133 }
1133 }
1134 new_cell.fromJSON(cell_data);
1134 new_cell.fromJSON(cell_data);
1135 this.select(current_index);
1135 this.select(current_index);
1136 this.undelete_backup = null;
1136 this.undelete_backup = null;
1137 this.undelete_index = null;
1137 this.undelete_index = null;
1138 }
1138 }
1139 $('#undelete_cell').addClass('disabled');
1139 $('#undelete_cell').addClass('disabled');
1140 }
1140 }
1141
1141
1142 // Split/merge
1142 // Split/merge
1143
1143
1144 /**
1144 /**
1145 * Split the selected cell into two, at the cursor.
1145 * Split the selected cell into two, at the cursor.
1146 *
1146 *
1147 * @method split_cell
1147 * @method split_cell
1148 */
1148 */
1149 Notebook.prototype.split_cell = function () {
1149 Notebook.prototype.split_cell = function () {
1150 // Todo: implement spliting for other cell types.
1150 // Todo: implement spliting for other cell types.
1151 var cell = this.get_selected_cell();
1151 var cell = this.get_selected_cell();
1152 if (cell.is_splittable()) {
1152 if (cell.is_splittable()) {
1153 var texta = cell.get_pre_cursor();
1153 var texta = cell.get_pre_cursor();
1154 var textb = cell.get_post_cursor();
1154 var textb = cell.get_post_cursor();
1155 if (cell instanceof IPython.CodeCell) {
1155 if (cell instanceof IPython.CodeCell) {
1156 cell.set_text(texta);
1156 cell.set_text(texta);
1157 var new_cell = this.insert_cell_below('code');
1157 var new_cell = this.insert_cell_below('code');
1158 new_cell.set_text(textb);
1158 new_cell.set_text(textb);
1159 } else if (cell instanceof IPython.MarkdownCell) {
1159 } else if (cell instanceof IPython.MarkdownCell) {
1160 cell.set_text(texta);
1160 cell.set_text(texta);
1161 cell.render();
1161 cell.render();
1162 var new_cell = this.insert_cell_below('markdown');
1162 var new_cell = this.insert_cell_below('markdown');
1163 new_cell.edit(); // editor must be visible to call set_text
1163 new_cell.edit(); // editor must be visible to call set_text
1164 new_cell.set_text(textb);
1164 new_cell.set_text(textb);
1165 new_cell.render();
1165 new_cell.render();
1166 }
1166 }
1167 };
1167 };
1168 };
1168 };
1169
1169
1170 /**
1170 /**
1171 * Combine the selected cell into the cell above it.
1171 * Combine the selected cell into the cell above it.
1172 *
1172 *
1173 * @method merge_cell_above
1173 * @method merge_cell_above
1174 */
1174 */
1175 Notebook.prototype.merge_cell_above = function () {
1175 Notebook.prototype.merge_cell_above = function () {
1176 var index = this.get_selected_index();
1176 var index = this.get_selected_index();
1177 var cell = this.get_cell(index);
1177 var cell = this.get_cell(index);
1178 if (!cell.is_mergeable()) {
1178 if (!cell.is_mergeable()) {
1179 return;
1179 return;
1180 }
1180 }
1181 if (index > 0) {
1181 if (index > 0) {
1182 var upper_cell = this.get_cell(index-1);
1182 var upper_cell = this.get_cell(index-1);
1183 if (!upper_cell.is_mergeable()) {
1183 if (!upper_cell.is_mergeable()) {
1184 return;
1184 return;
1185 }
1185 }
1186 var upper_text = upper_cell.get_text();
1186 var upper_text = upper_cell.get_text();
1187 var text = cell.get_text();
1187 var text = cell.get_text();
1188 if (cell instanceof IPython.CodeCell) {
1188 if (cell instanceof IPython.CodeCell) {
1189 cell.set_text(upper_text+'\n'+text);
1189 cell.set_text(upper_text+'\n'+text);
1190 } else if (cell instanceof IPython.MarkdownCell) {
1190 } else if (cell instanceof IPython.MarkdownCell) {
1191 cell.edit();
1191 cell.edit();
1192 cell.set_text(upper_text+'\n'+text);
1192 cell.set_text(upper_text+'\n'+text);
1193 cell.render();
1193 cell.render();
1194 };
1194 };
1195 this.delete_cell(index-1);
1195 this.delete_cell(index-1);
1196 this.select(this.find_cell_index(cell));
1196 this.select(this.find_cell_index(cell));
1197 };
1197 };
1198 };
1198 };
1199
1199
1200 /**
1200 /**
1201 * Combine the selected cell into the cell below it.
1201 * Combine the selected cell into the cell below it.
1202 *
1202 *
1203 * @method merge_cell_below
1203 * @method merge_cell_below
1204 */
1204 */
1205 Notebook.prototype.merge_cell_below = function () {
1205 Notebook.prototype.merge_cell_below = function () {
1206 var index = this.get_selected_index();
1206 var index = this.get_selected_index();
1207 var cell = this.get_cell(index);
1207 var cell = this.get_cell(index);
1208 if (!cell.is_mergeable()) {
1208 if (!cell.is_mergeable()) {
1209 return;
1209 return;
1210 }
1210 }
1211 if (index < this.ncells()-1) {
1211 if (index < this.ncells()-1) {
1212 var lower_cell = this.get_cell(index+1);
1212 var lower_cell = this.get_cell(index+1);
1213 if (!lower_cell.is_mergeable()) {
1213 if (!lower_cell.is_mergeable()) {
1214 return;
1214 return;
1215 }
1215 }
1216 var lower_text = lower_cell.get_text();
1216 var lower_text = lower_cell.get_text();
1217 var text = cell.get_text();
1217 var text = cell.get_text();
1218 if (cell instanceof IPython.CodeCell) {
1218 if (cell instanceof IPython.CodeCell) {
1219 cell.set_text(text+'\n'+lower_text);
1219 cell.set_text(text+'\n'+lower_text);
1220 } else if (cell instanceof IPython.MarkdownCell) {
1220 } else if (cell instanceof IPython.MarkdownCell) {
1221 cell.edit();
1221 cell.edit();
1222 cell.set_text(text+'\n'+lower_text);
1222 cell.set_text(text+'\n'+lower_text);
1223 cell.render();
1223 cell.render();
1224 };
1224 };
1225 this.delete_cell(index+1);
1225 this.delete_cell(index+1);
1226 this.select(this.find_cell_index(cell));
1226 this.select(this.find_cell_index(cell));
1227 };
1227 };
1228 };
1228 };
1229
1229
1230
1230
1231 // Cell collapsing and output clearing
1231 // Cell collapsing and output clearing
1232
1232
1233 /**
1233 /**
1234 * Hide a cell's output.
1234 * Hide a cell's output.
1235 *
1235 *
1236 * @method collapse
1236 * @method collapse
1237 * @param {Number} index A cell's numeric index
1237 * @param {Number} index A cell's numeric index
1238 */
1238 */
1239 Notebook.prototype.collapse = function (index) {
1239 Notebook.prototype.collapse = function (index) {
1240 var i = this.index_or_selected(index);
1240 var i = this.index_or_selected(index);
1241 this.get_cell(i).collapse();
1241 this.get_cell(i).collapse();
1242 this.set_dirty(true);
1242 this.set_dirty(true);
1243 };
1243 };
1244
1244
1245 /**
1245 /**
1246 * Show a cell's output.
1246 * Show a cell's output.
1247 *
1247 *
1248 * @method expand
1248 * @method expand
1249 * @param {Number} index A cell's numeric index
1249 * @param {Number} index A cell's numeric index
1250 */
1250 */
1251 Notebook.prototype.expand = function (index) {
1251 Notebook.prototype.expand = function (index) {
1252 var i = this.index_or_selected(index);
1252 var i = this.index_or_selected(index);
1253 this.get_cell(i).expand();
1253 this.get_cell(i).expand();
1254 this.set_dirty(true);
1254 this.set_dirty(true);
1255 };
1255 };
1256
1256
1257 /** Toggle whether a cell's output is collapsed or expanded.
1257 /** Toggle whether a cell's output is collapsed or expanded.
1258 *
1258 *
1259 * @method toggle_output
1259 * @method toggle_output
1260 * @param {Number} index A cell's numeric index
1260 * @param {Number} index A cell's numeric index
1261 */
1261 */
1262 Notebook.prototype.toggle_output = function (index) {
1262 Notebook.prototype.toggle_output = function (index) {
1263 var i = this.index_or_selected(index);
1263 var i = this.index_or_selected(index);
1264 this.get_cell(i).toggle_output();
1264 this.get_cell(i).toggle_output();
1265 this.set_dirty(true);
1265 this.set_dirty(true);
1266 };
1266 };
1267
1267
1268 /**
1268 /**
1269 * Toggle a scrollbar for long cell outputs.
1269 * Toggle a scrollbar for long cell outputs.
1270 *
1270 *
1271 * @method toggle_output_scroll
1271 * @method toggle_output_scroll
1272 * @param {Number} index A cell's numeric index
1272 * @param {Number} index A cell's numeric index
1273 */
1273 */
1274 Notebook.prototype.toggle_output_scroll = function (index) {
1274 Notebook.prototype.toggle_output_scroll = function (index) {
1275 var i = this.index_or_selected(index);
1275 var i = this.index_or_selected(index);
1276 this.get_cell(i).toggle_output_scroll();
1276 this.get_cell(i).toggle_output_scroll();
1277 };
1277 };
1278
1278
1279 /**
1279 /**
1280 * Hide each code cell's output area.
1280 * Hide each code cell's output area.
1281 *
1281 *
1282 * @method collapse_all_output
1282 * @method collapse_all_output
1283 */
1283 */
1284 Notebook.prototype.collapse_all_output = function () {
1284 Notebook.prototype.collapse_all_output = function () {
1285 var ncells = this.ncells();
1285 var ncells = this.ncells();
1286 var cells = this.get_cells();
1286 var cells = this.get_cells();
1287 for (var i=0; i<ncells; i++) {
1287 for (var i=0; i<ncells; i++) {
1288 if (cells[i] instanceof IPython.CodeCell) {
1288 if (cells[i] instanceof IPython.CodeCell) {
1289 cells[i].output_area.collapse();
1289 cells[i].output_area.collapse();
1290 }
1290 }
1291 };
1291 };
1292 // this should not be set if the `collapse` key is removed from nbformat
1292 // this should not be set if the `collapse` key is removed from nbformat
1293 this.set_dirty(true);
1293 this.set_dirty(true);
1294 };
1294 };
1295
1295
1296 /**
1296 /**
1297 * Expand each code cell's output area, and add a scrollbar for long output.
1297 * Expand each code cell's output area, and add a scrollbar for long output.
1298 *
1298 *
1299 * @method scroll_all_output
1299 * @method scroll_all_output
1300 */
1300 */
1301 Notebook.prototype.scroll_all_output = function () {
1301 Notebook.prototype.scroll_all_output = function () {
1302 var ncells = this.ncells();
1302 var ncells = this.ncells();
1303 var cells = this.get_cells();
1303 var cells = this.get_cells();
1304 for (var i=0; i<ncells; i++) {
1304 for (var i=0; i<ncells; i++) {
1305 if (cells[i] instanceof IPython.CodeCell) {
1305 if (cells[i] instanceof IPython.CodeCell) {
1306 cells[i].output_area.expand();
1306 cells[i].output_area.expand();
1307 cells[i].output_area.scroll_if_long();
1307 cells[i].output_area.scroll_if_long();
1308 }
1308 }
1309 };
1309 };
1310 // this should not be set if the `collapse` key is removed from nbformat
1310 // this should not be set if the `collapse` key is removed from nbformat
1311 this.set_dirty(true);
1311 this.set_dirty(true);
1312 };
1312 };
1313
1313
1314 /**
1314 /**
1315 * Expand each code cell's output area, and remove scrollbars.
1315 * Expand each code cell's output area, and remove scrollbars.
1316 *
1316 *
1317 * @method expand_all_output
1317 * @method expand_all_output
1318 */
1318 */
1319 Notebook.prototype.expand_all_output = function () {
1319 Notebook.prototype.expand_all_output = function () {
1320 var ncells = this.ncells();
1320 var ncells = this.ncells();
1321 var cells = this.get_cells();
1321 var cells = this.get_cells();
1322 for (var i=0; i<ncells; i++) {
1322 for (var i=0; i<ncells; i++) {
1323 if (cells[i] instanceof IPython.CodeCell) {
1323 if (cells[i] instanceof IPython.CodeCell) {
1324 cells[i].output_area.expand();
1324 cells[i].output_area.expand();
1325 cells[i].output_area.unscroll_area();
1325 cells[i].output_area.unscroll_area();
1326 }
1326 }
1327 };
1327 };
1328 // this should not be set if the `collapse` key is removed from nbformat
1328 // this should not be set if the `collapse` key is removed from nbformat
1329 this.set_dirty(true);
1329 this.set_dirty(true);
1330 };
1330 };
1331
1331
1332 /**
1332 /**
1333 * Clear each code cell's output area.
1333 * Clear each code cell's output area.
1334 *
1334 *
1335 * @method clear_all_output
1335 * @method clear_all_output
1336 */
1336 */
1337 Notebook.prototype.clear_all_output = function () {
1337 Notebook.prototype.clear_all_output = function () {
1338 var ncells = this.ncells();
1338 var ncells = this.ncells();
1339 var cells = this.get_cells();
1339 var cells = this.get_cells();
1340 for (var i=0; i<ncells; i++) {
1340 for (var i=0; i<ncells; i++) {
1341 if (cells[i] instanceof IPython.CodeCell) {
1341 if (cells[i] instanceof IPython.CodeCell) {
1342 cells[i].clear_output(true,true,true);
1342 cells[i].clear_output();
1343 // Make all In[] prompts blank, as well
1343 // Make all In[] prompts blank, as well
1344 // TODO: make this configurable (via checkbox?)
1344 // TODO: make this configurable (via checkbox?)
1345 cells[i].set_input_prompt();
1345 cells[i].set_input_prompt();
1346 }
1346 }
1347 };
1347 };
1348 this.set_dirty(true);
1348 this.set_dirty(true);
1349 };
1349 };
1350
1350
1351
1351
1352 // Other cell functions: line numbers, ...
1352 // Other cell functions: line numbers, ...
1353
1353
1354 /**
1354 /**
1355 * Toggle line numbers in the selected cell's input area.
1355 * Toggle line numbers in the selected cell's input area.
1356 *
1356 *
1357 * @method cell_toggle_line_numbers
1357 * @method cell_toggle_line_numbers
1358 */
1358 */
1359 Notebook.prototype.cell_toggle_line_numbers = function() {
1359 Notebook.prototype.cell_toggle_line_numbers = function() {
1360 this.get_selected_cell().toggle_line_numbers();
1360 this.get_selected_cell().toggle_line_numbers();
1361 };
1361 };
1362
1362
1363 // Kernel related things
1363 // Kernel related things
1364
1364
1365 /**
1365 /**
1366 * Start a new kernel and set it on each code cell.
1366 * Start a new kernel and set it on each code cell.
1367 *
1367 *
1368 * @method start_kernel
1368 * @method start_kernel
1369 */
1369 */
1370 Notebook.prototype.start_kernel = function () {
1370 Notebook.prototype.start_kernel = function () {
1371 var base_url = $('body').data('baseKernelUrl') + "kernels";
1371 var base_url = $('body').data('baseKernelUrl') + "kernels";
1372 this.kernel = new IPython.Kernel(base_url);
1372 this.kernel = new IPython.Kernel(base_url);
1373 this.kernel.start(this.notebook_id);
1373 this.kernel.start(this.notebook_id);
1374 // Now that the kernel has been created, tell the CodeCells about it.
1374 // Now that the kernel has been created, tell the CodeCells about it.
1375 var ncells = this.ncells();
1375 var ncells = this.ncells();
1376 for (var i=0; i<ncells; i++) {
1376 for (var i=0; i<ncells; i++) {
1377 var cell = this.get_cell(i);
1377 var cell = this.get_cell(i);
1378 if (cell instanceof IPython.CodeCell) {
1378 if (cell instanceof IPython.CodeCell) {
1379 cell.set_kernel(this.kernel)
1379 cell.set_kernel(this.kernel)
1380 };
1380 };
1381 };
1381 };
1382 };
1382 };
1383
1383
1384 /**
1384 /**
1385 * Prompt the user to restart the IPython kernel.
1385 * Prompt the user to restart the IPython kernel.
1386 *
1386 *
1387 * @method restart_kernel
1387 * @method restart_kernel
1388 */
1388 */
1389 Notebook.prototype.restart_kernel = function () {
1389 Notebook.prototype.restart_kernel = function () {
1390 var that = this;
1390 var that = this;
1391 IPython.dialog.modal({
1391 IPython.dialog.modal({
1392 title : "Restart kernel or continue running?",
1392 title : "Restart kernel or continue running?",
1393 body : $("<p/>").html(
1393 body : $("<p/>").html(
1394 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1394 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1395 ),
1395 ),
1396 buttons : {
1396 buttons : {
1397 "Continue running" : {},
1397 "Continue running" : {},
1398 "Restart" : {
1398 "Restart" : {
1399 "class" : "btn-danger",
1399 "class" : "btn-danger",
1400 "click" : function() {
1400 "click" : function() {
1401 that.kernel.restart();
1401 that.kernel.restart();
1402 }
1402 }
1403 }
1403 }
1404 }
1404 }
1405 });
1405 });
1406 };
1406 };
1407
1407
1408 /**
1408 /**
1409 * Run the selected cell.
1409 * Run the selected cell.
1410 *
1410 *
1411 * Execute or render cell outputs.
1411 * Execute or render cell outputs.
1412 *
1412 *
1413 * @method execute_selected_cell
1413 * @method execute_selected_cell
1414 * @param {Object} options Customize post-execution behavior
1414 * @param {Object} options Customize post-execution behavior
1415 */
1415 */
1416 Notebook.prototype.execute_selected_cell = function (options) {
1416 Notebook.prototype.execute_selected_cell = function (options) {
1417 // add_new: should a new cell be added if we are at the end of the nb
1417 // add_new: should a new cell be added if we are at the end of the nb
1418 // terminal: execute in terminal mode, which stays in the current cell
1418 // terminal: execute in terminal mode, which stays in the current cell
1419 var default_options = {terminal: false, add_new: true};
1419 var default_options = {terminal: false, add_new: true};
1420 $.extend(default_options, options);
1420 $.extend(default_options, options);
1421 var that = this;
1421 var that = this;
1422 var cell = that.get_selected_cell();
1422 var cell = that.get_selected_cell();
1423 var cell_index = that.find_cell_index(cell);
1423 var cell_index = that.find_cell_index(cell);
1424 if (cell instanceof IPython.CodeCell) {
1424 if (cell instanceof IPython.CodeCell) {
1425 cell.execute();
1425 cell.execute();
1426 }
1426 }
1427 if (default_options.terminal) {
1427 if (default_options.terminal) {
1428 cell.select_all();
1428 cell.select_all();
1429 } else {
1429 } else {
1430 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1430 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1431 that.insert_cell_below('code');
1431 that.insert_cell_below('code');
1432 // If we are adding a new cell at the end, scroll down to show it.
1432 // If we are adding a new cell at the end, scroll down to show it.
1433 that.scroll_to_bottom();
1433 that.scroll_to_bottom();
1434 } else {
1434 } else {
1435 that.select(cell_index+1);
1435 that.select(cell_index+1);
1436 };
1436 };
1437 };
1437 };
1438 this.set_dirty(true);
1438 this.set_dirty(true);
1439 };
1439 };
1440
1440
1441 /**
1441 /**
1442 * Execute all cells below the selected cell.
1442 * Execute all cells below the selected cell.
1443 *
1443 *
1444 * @method execute_cells_below
1444 * @method execute_cells_below
1445 */
1445 */
1446 Notebook.prototype.execute_cells_below = function () {
1446 Notebook.prototype.execute_cells_below = function () {
1447 this.execute_cell_range(this.get_selected_index(), this.ncells());
1447 this.execute_cell_range(this.get_selected_index(), this.ncells());
1448 this.scroll_to_bottom();
1448 this.scroll_to_bottom();
1449 };
1449 };
1450
1450
1451 /**
1451 /**
1452 * Execute all cells above the selected cell.
1452 * Execute all cells above the selected cell.
1453 *
1453 *
1454 * @method execute_cells_above
1454 * @method execute_cells_above
1455 */
1455 */
1456 Notebook.prototype.execute_cells_above = function () {
1456 Notebook.prototype.execute_cells_above = function () {
1457 this.execute_cell_range(0, this.get_selected_index());
1457 this.execute_cell_range(0, this.get_selected_index());
1458 };
1458 };
1459
1459
1460 /**
1460 /**
1461 * Execute all cells.
1461 * Execute all cells.
1462 *
1462 *
1463 * @method execute_all_cells
1463 * @method execute_all_cells
1464 */
1464 */
1465 Notebook.prototype.execute_all_cells = function () {
1465 Notebook.prototype.execute_all_cells = function () {
1466 this.execute_cell_range(0, this.ncells());
1466 this.execute_cell_range(0, this.ncells());
1467 this.scroll_to_bottom();
1467 this.scroll_to_bottom();
1468 };
1468 };
1469
1469
1470 /**
1470 /**
1471 * Execute a contiguous range of cells.
1471 * Execute a contiguous range of cells.
1472 *
1472 *
1473 * @method execute_cell_range
1473 * @method execute_cell_range
1474 * @param {Number} start Index of the first cell to execute (inclusive)
1474 * @param {Number} start Index of the first cell to execute (inclusive)
1475 * @param {Number} end Index of the last cell to execute (exclusive)
1475 * @param {Number} end Index of the last cell to execute (exclusive)
1476 */
1476 */
1477 Notebook.prototype.execute_cell_range = function (start, end) {
1477 Notebook.prototype.execute_cell_range = function (start, end) {
1478 for (var i=start; i<end; i++) {
1478 for (var i=start; i<end; i++) {
1479 this.select(i);
1479 this.select(i);
1480 this.execute_selected_cell({add_new:false});
1480 this.execute_selected_cell({add_new:false});
1481 };
1481 };
1482 };
1482 };
1483
1483
1484 // Persistance and loading
1484 // Persistance and loading
1485
1485
1486 /**
1486 /**
1487 * Getter method for this notebook's ID.
1487 * Getter method for this notebook's ID.
1488 *
1488 *
1489 * @method get_notebook_id
1489 * @method get_notebook_id
1490 * @return {String} This notebook's ID
1490 * @return {String} This notebook's ID
1491 */
1491 */
1492 Notebook.prototype.get_notebook_id = function () {
1492 Notebook.prototype.get_notebook_id = function () {
1493 return this.notebook_id;
1493 return this.notebook_id;
1494 };
1494 };
1495
1495
1496 /**
1496 /**
1497 * Getter method for this notebook's name.
1497 * Getter method for this notebook's name.
1498 *
1498 *
1499 * @method get_notebook_name
1499 * @method get_notebook_name
1500 * @return {String} This notebook's name
1500 * @return {String} This notebook's name
1501 */
1501 */
1502 Notebook.prototype.get_notebook_name = function () {
1502 Notebook.prototype.get_notebook_name = function () {
1503 return this.notebook_name;
1503 return this.notebook_name;
1504 };
1504 };
1505
1505
1506 /**
1506 /**
1507 * Setter method for this notebook's name.
1507 * Setter method for this notebook's name.
1508 *
1508 *
1509 * @method set_notebook_name
1509 * @method set_notebook_name
1510 * @param {String} name A new name for this notebook
1510 * @param {String} name A new name for this notebook
1511 */
1511 */
1512 Notebook.prototype.set_notebook_name = function (name) {
1512 Notebook.prototype.set_notebook_name = function (name) {
1513 this.notebook_name = name;
1513 this.notebook_name = name;
1514 };
1514 };
1515
1515
1516 /**
1516 /**
1517 * Check that a notebook's name is valid.
1517 * Check that a notebook's name is valid.
1518 *
1518 *
1519 * @method test_notebook_name
1519 * @method test_notebook_name
1520 * @param {String} nbname A name for this notebook
1520 * @param {String} nbname A name for this notebook
1521 * @return {Boolean} True if the name is valid, false if invalid
1521 * @return {Boolean} True if the name is valid, false if invalid
1522 */
1522 */
1523 Notebook.prototype.test_notebook_name = function (nbname) {
1523 Notebook.prototype.test_notebook_name = function (nbname) {
1524 nbname = nbname || '';
1524 nbname = nbname || '';
1525 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1525 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1526 return true;
1526 return true;
1527 } else {
1527 } else {
1528 return false;
1528 return false;
1529 };
1529 };
1530 };
1530 };
1531
1531
1532 /**
1532 /**
1533 * Load a notebook from JSON (.ipynb).
1533 * Load a notebook from JSON (.ipynb).
1534 *
1534 *
1535 * This currently handles one worksheet: others are deleted.
1535 * This currently handles one worksheet: others are deleted.
1536 *
1536 *
1537 * @method fromJSON
1537 * @method fromJSON
1538 * @param {Object} data JSON representation of a notebook
1538 * @param {Object} data JSON representation of a notebook
1539 */
1539 */
1540 Notebook.prototype.fromJSON = function (data) {
1540 Notebook.prototype.fromJSON = function (data) {
1541 var ncells = this.ncells();
1541 var ncells = this.ncells();
1542 var i;
1542 var i;
1543 for (i=0; i<ncells; i++) {
1543 for (i=0; i<ncells; i++) {
1544 // Always delete cell 0 as they get renumbered as they are deleted.
1544 // Always delete cell 0 as they get renumbered as they are deleted.
1545 this.delete_cell(0);
1545 this.delete_cell(0);
1546 };
1546 };
1547 // Save the metadata and name.
1547 // Save the metadata and name.
1548 this.metadata = data.metadata;
1548 this.metadata = data.metadata;
1549 this.notebook_name = data.metadata.name;
1549 this.notebook_name = data.metadata.name;
1550 // Only handle 1 worksheet for now.
1550 // Only handle 1 worksheet for now.
1551 var worksheet = data.worksheets[0];
1551 var worksheet = data.worksheets[0];
1552 if (worksheet !== undefined) {
1552 if (worksheet !== undefined) {
1553 if (worksheet.metadata) {
1553 if (worksheet.metadata) {
1554 this.worksheet_metadata = worksheet.metadata;
1554 this.worksheet_metadata = worksheet.metadata;
1555 }
1555 }
1556 var new_cells = worksheet.cells;
1556 var new_cells = worksheet.cells;
1557 ncells = new_cells.length;
1557 ncells = new_cells.length;
1558 var cell_data = null;
1558 var cell_data = null;
1559 var new_cell = null;
1559 var new_cell = null;
1560 for (i=0; i<ncells; i++) {
1560 for (i=0; i<ncells; i++) {
1561 cell_data = new_cells[i];
1561 cell_data = new_cells[i];
1562 // VERSIONHACK: plaintext -> raw
1562 // VERSIONHACK: plaintext -> raw
1563 // handle never-released plaintext name for raw cells
1563 // handle never-released plaintext name for raw cells
1564 if (cell_data.cell_type === 'plaintext'){
1564 if (cell_data.cell_type === 'plaintext'){
1565 cell_data.cell_type = 'raw';
1565 cell_data.cell_type = 'raw';
1566 }
1566 }
1567
1567
1568 new_cell = this.insert_cell_below(cell_data.cell_type);
1568 new_cell = this.insert_cell_below(cell_data.cell_type);
1569 new_cell.fromJSON(cell_data);
1569 new_cell.fromJSON(cell_data);
1570 };
1570 };
1571 };
1571 };
1572 if (data.worksheets.length > 1) {
1572 if (data.worksheets.length > 1) {
1573 IPython.dialog.modal({
1573 IPython.dialog.modal({
1574 title : "Multiple worksheets",
1574 title : "Multiple worksheets",
1575 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1575 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1576 "but this version of IPython can only handle the first. " +
1576 "but this version of IPython can only handle the first. " +
1577 "If you save this notebook, worksheets after the first will be lost.",
1577 "If you save this notebook, worksheets after the first will be lost.",
1578 buttons : {
1578 buttons : {
1579 OK : {
1579 OK : {
1580 class : "btn-danger"
1580 class : "btn-danger"
1581 }
1581 }
1582 }
1582 }
1583 });
1583 });
1584 }
1584 }
1585 };
1585 };
1586
1586
1587 /**
1587 /**
1588 * Dump this notebook into a JSON-friendly object.
1588 * Dump this notebook into a JSON-friendly object.
1589 *
1589 *
1590 * @method toJSON
1590 * @method toJSON
1591 * @return {Object} A JSON-friendly representation of this notebook.
1591 * @return {Object} A JSON-friendly representation of this notebook.
1592 */
1592 */
1593 Notebook.prototype.toJSON = function () {
1593 Notebook.prototype.toJSON = function () {
1594 var cells = this.get_cells();
1594 var cells = this.get_cells();
1595 var ncells = cells.length;
1595 var ncells = cells.length;
1596 var cell_array = new Array(ncells);
1596 var cell_array = new Array(ncells);
1597 for (var i=0; i<ncells; i++) {
1597 for (var i=0; i<ncells; i++) {
1598 cell_array[i] = cells[i].toJSON();
1598 cell_array[i] = cells[i].toJSON();
1599 };
1599 };
1600 var data = {
1600 var data = {
1601 // Only handle 1 worksheet for now.
1601 // Only handle 1 worksheet for now.
1602 worksheets : [{
1602 worksheets : [{
1603 cells: cell_array,
1603 cells: cell_array,
1604 metadata: this.worksheet_metadata
1604 metadata: this.worksheet_metadata
1605 }],
1605 }],
1606 metadata : this.metadata
1606 metadata : this.metadata
1607 };
1607 };
1608 return data;
1608 return data;
1609 };
1609 };
1610
1610
1611 /**
1611 /**
1612 * Start an autosave timer, for periodically saving the notebook.
1612 * Start an autosave timer, for periodically saving the notebook.
1613 *
1613 *
1614 * @method set_autosave_interval
1614 * @method set_autosave_interval
1615 * @param {Integer} interval the autosave interval in milliseconds
1615 * @param {Integer} interval the autosave interval in milliseconds
1616 */
1616 */
1617 Notebook.prototype.set_autosave_interval = function (interval) {
1617 Notebook.prototype.set_autosave_interval = function (interval) {
1618 var that = this;
1618 var that = this;
1619 // clear previous interval, so we don't get simultaneous timers
1619 // clear previous interval, so we don't get simultaneous timers
1620 if (this.autosave_timer) {
1620 if (this.autosave_timer) {
1621 clearInterval(this.autosave_timer);
1621 clearInterval(this.autosave_timer);
1622 }
1622 }
1623
1623
1624 this.autosave_interval = this.minimum_autosave_interval = interval;
1624 this.autosave_interval = this.minimum_autosave_interval = interval;
1625 if (interval) {
1625 if (interval) {
1626 this.autosave_timer = setInterval(function() {
1626 this.autosave_timer = setInterval(function() {
1627 if (that.dirty) {
1627 if (that.dirty) {
1628 that.save_notebook();
1628 that.save_notebook();
1629 }
1629 }
1630 }, interval);
1630 }, interval);
1631 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1631 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1632 } else {
1632 } else {
1633 this.autosave_timer = null;
1633 this.autosave_timer = null;
1634 $([IPython.events]).trigger("autosave_disabled.Notebook");
1634 $([IPython.events]).trigger("autosave_disabled.Notebook");
1635 };
1635 };
1636 };
1636 };
1637
1637
1638 /**
1638 /**
1639 * Save this notebook on the server.
1639 * Save this notebook on the server.
1640 *
1640 *
1641 * @method save_notebook
1641 * @method save_notebook
1642 */
1642 */
1643 Notebook.prototype.save_notebook = function () {
1643 Notebook.prototype.save_notebook = function () {
1644 // We may want to move the name/id/nbformat logic inside toJSON?
1644 // We may want to move the name/id/nbformat logic inside toJSON?
1645 var data = this.toJSON();
1645 var data = this.toJSON();
1646 data.metadata.name = this.notebook_name;
1646 data.metadata.name = this.notebook_name;
1647 data.nbformat = this.nbformat;
1647 data.nbformat = this.nbformat;
1648 data.nbformat_minor = this.nbformat_minor;
1648 data.nbformat_minor = this.nbformat_minor;
1649
1649
1650 // time the ajax call for autosave tuning purposes.
1650 // time the ajax call for autosave tuning purposes.
1651 var start = new Date().getTime();
1651 var start = new Date().getTime();
1652
1652
1653 // We do the call with settings so we can set cache to false.
1653 // We do the call with settings so we can set cache to false.
1654 var settings = {
1654 var settings = {
1655 processData : false,
1655 processData : false,
1656 cache : false,
1656 cache : false,
1657 type : "PUT",
1657 type : "PUT",
1658 data : JSON.stringify(data),
1658 data : JSON.stringify(data),
1659 headers : {'Content-Type': 'application/json'},
1659 headers : {'Content-Type': 'application/json'},
1660 success : $.proxy(this.save_notebook_success, this, start),
1660 success : $.proxy(this.save_notebook_success, this, start),
1661 error : $.proxy(this.save_notebook_error, this)
1661 error : $.proxy(this.save_notebook_error, this)
1662 };
1662 };
1663 $([IPython.events]).trigger('notebook_saving.Notebook');
1663 $([IPython.events]).trigger('notebook_saving.Notebook');
1664 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1664 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1665 $.ajax(url, settings);
1665 $.ajax(url, settings);
1666 };
1666 };
1667
1667
1668 /**
1668 /**
1669 * Success callback for saving a notebook.
1669 * Success callback for saving a notebook.
1670 *
1670 *
1671 * @method save_notebook_success
1671 * @method save_notebook_success
1672 * @param {Integer} start the time when the save request started
1672 * @param {Integer} start the time when the save request started
1673 * @param {Object} data JSON representation of a notebook
1673 * @param {Object} data JSON representation of a notebook
1674 * @param {String} status Description of response status
1674 * @param {String} status Description of response status
1675 * @param {jqXHR} xhr jQuery Ajax object
1675 * @param {jqXHR} xhr jQuery Ajax object
1676 */
1676 */
1677 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1677 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1678 this.set_dirty(false);
1678 this.set_dirty(false);
1679 $([IPython.events]).trigger('notebook_saved.Notebook');
1679 $([IPython.events]).trigger('notebook_saved.Notebook');
1680 this._update_autosave_interval(start);
1680 this._update_autosave_interval(start);
1681 if (this._checkpoint_after_save) {
1681 if (this._checkpoint_after_save) {
1682 this.create_checkpoint();
1682 this.create_checkpoint();
1683 this._checkpoint_after_save = false;
1683 this._checkpoint_after_save = false;
1684 };
1684 };
1685 };
1685 };
1686
1686
1687 /**
1687 /**
1688 * update the autosave interval based on how long the last save took
1688 * update the autosave interval based on how long the last save took
1689 *
1689 *
1690 * @method _update_autosave_interval
1690 * @method _update_autosave_interval
1691 * @param {Integer} timestamp when the save request started
1691 * @param {Integer} timestamp when the save request started
1692 */
1692 */
1693 Notebook.prototype._update_autosave_interval = function (start) {
1693 Notebook.prototype._update_autosave_interval = function (start) {
1694 var duration = (new Date().getTime() - start);
1694 var duration = (new Date().getTime() - start);
1695 if (this.autosave_interval) {
1695 if (this.autosave_interval) {
1696 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1696 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1697 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1697 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1698 // round to 10 seconds, otherwise we will be setting a new interval too often
1698 // round to 10 seconds, otherwise we will be setting a new interval too often
1699 interval = 10000 * Math.round(interval / 10000);
1699 interval = 10000 * Math.round(interval / 10000);
1700 // set new interval, if it's changed
1700 // set new interval, if it's changed
1701 if (interval != this.autosave_interval) {
1701 if (interval != this.autosave_interval) {
1702 this.set_autosave_interval(interval);
1702 this.set_autosave_interval(interval);
1703 }
1703 }
1704 }
1704 }
1705 };
1705 };
1706
1706
1707 /**
1707 /**
1708 * Failure callback for saving a notebook.
1708 * Failure callback for saving a notebook.
1709 *
1709 *
1710 * @method save_notebook_error
1710 * @method save_notebook_error
1711 * @param {jqXHR} xhr jQuery Ajax object
1711 * @param {jqXHR} xhr jQuery Ajax object
1712 * @param {String} status Description of response status
1712 * @param {String} status Description of response status
1713 * @param {String} error_msg HTTP error message
1713 * @param {String} error_msg HTTP error message
1714 */
1714 */
1715 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1715 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1716 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1716 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1717 };
1717 };
1718
1718
1719 /**
1719 /**
1720 * Request a notebook's data from the server.
1720 * Request a notebook's data from the server.
1721 *
1721 *
1722 * @method load_notebook
1722 * @method load_notebook
1723 * @param {String} notebook_id A notebook to load
1723 * @param {String} notebook_id A notebook to load
1724 */
1724 */
1725 Notebook.prototype.load_notebook = function (notebook_id) {
1725 Notebook.prototype.load_notebook = function (notebook_id) {
1726 var that = this;
1726 var that = this;
1727 this.notebook_id = notebook_id;
1727 this.notebook_id = notebook_id;
1728 // We do the call with settings so we can set cache to false.
1728 // We do the call with settings so we can set cache to false.
1729 var settings = {
1729 var settings = {
1730 processData : false,
1730 processData : false,
1731 cache : false,
1731 cache : false,
1732 type : "GET",
1732 type : "GET",
1733 dataType : "json",
1733 dataType : "json",
1734 success : $.proxy(this.load_notebook_success,this),
1734 success : $.proxy(this.load_notebook_success,this),
1735 error : $.proxy(this.load_notebook_error,this),
1735 error : $.proxy(this.load_notebook_error,this),
1736 };
1736 };
1737 $([IPython.events]).trigger('notebook_loading.Notebook');
1737 $([IPython.events]).trigger('notebook_loading.Notebook');
1738 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1738 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1739 $.ajax(url, settings);
1739 $.ajax(url, settings);
1740 };
1740 };
1741
1741
1742 /**
1742 /**
1743 * Success callback for loading a notebook from the server.
1743 * Success callback for loading a notebook from the server.
1744 *
1744 *
1745 * Load notebook data from the JSON response.
1745 * Load notebook data from the JSON response.
1746 *
1746 *
1747 * @method load_notebook_success
1747 * @method load_notebook_success
1748 * @param {Object} data JSON representation of a notebook
1748 * @param {Object} data JSON representation of a notebook
1749 * @param {String} status Description of response status
1749 * @param {String} status Description of response status
1750 * @param {jqXHR} xhr jQuery Ajax object
1750 * @param {jqXHR} xhr jQuery Ajax object
1751 */
1751 */
1752 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1752 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1753 this.fromJSON(data);
1753 this.fromJSON(data);
1754 if (this.ncells() === 0) {
1754 if (this.ncells() === 0) {
1755 this.insert_cell_below('code');
1755 this.insert_cell_below('code');
1756 };
1756 };
1757 this.set_dirty(false);
1757 this.set_dirty(false);
1758 this.select(0);
1758 this.select(0);
1759 this.scroll_to_top();
1759 this.scroll_to_top();
1760 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1760 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1761 var msg = "This notebook has been converted from an older " +
1761 var msg = "This notebook has been converted from an older " +
1762 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1762 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1763 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1763 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1764 "newer notebook format will be used and older versions of IPython " +
1764 "newer notebook format will be used and older versions of IPython " +
1765 "may not be able to read it. To keep the older version, close the " +
1765 "may not be able to read it. To keep the older version, close the " +
1766 "notebook without saving it.";
1766 "notebook without saving it.";
1767 IPython.dialog.modal({
1767 IPython.dialog.modal({
1768 title : "Notebook converted",
1768 title : "Notebook converted",
1769 body : msg,
1769 body : msg,
1770 buttons : {
1770 buttons : {
1771 OK : {
1771 OK : {
1772 class : "btn-primary"
1772 class : "btn-primary"
1773 }
1773 }
1774 }
1774 }
1775 });
1775 });
1776 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1776 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1777 var that = this;
1777 var that = this;
1778 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1778 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1779 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1779 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1780 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1780 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1781 this_vs + ". You can still work with this notebook, but some features " +
1781 this_vs + ". You can still work with this notebook, but some features " +
1782 "introduced in later notebook versions may not be available."
1782 "introduced in later notebook versions may not be available."
1783
1783
1784 IPython.dialog.modal({
1784 IPython.dialog.modal({
1785 title : "Newer Notebook",
1785 title : "Newer Notebook",
1786 body : msg,
1786 body : msg,
1787 buttons : {
1787 buttons : {
1788 OK : {
1788 OK : {
1789 class : "btn-danger"
1789 class : "btn-danger"
1790 }
1790 }
1791 }
1791 }
1792 });
1792 });
1793
1793
1794 }
1794 }
1795
1795
1796 // Create the kernel after the notebook is completely loaded to prevent
1796 // Create the kernel after the notebook is completely loaded to prevent
1797 // code execution upon loading, which is a security risk.
1797 // code execution upon loading, which is a security risk.
1798 this.start_kernel();
1798 this.start_kernel();
1799 // load our checkpoint list
1799 // load our checkpoint list
1800 IPython.notebook.list_checkpoints();
1800 IPython.notebook.list_checkpoints();
1801
1801
1802 $([IPython.events]).trigger('notebook_loaded.Notebook');
1802 $([IPython.events]).trigger('notebook_loaded.Notebook');
1803 };
1803 };
1804
1804
1805 /**
1805 /**
1806 * Failure callback for loading a notebook from the server.
1806 * Failure callback for loading a notebook from the server.
1807 *
1807 *
1808 * @method load_notebook_error
1808 * @method load_notebook_error
1809 * @param {jqXHR} xhr jQuery Ajax object
1809 * @param {jqXHR} xhr jQuery Ajax object
1810 * @param {String} textStatus Description of response status
1810 * @param {String} textStatus Description of response status
1811 * @param {String} errorThrow HTTP error message
1811 * @param {String} errorThrow HTTP error message
1812 */
1812 */
1813 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1813 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1814 if (xhr.status === 400) {
1814 if (xhr.status === 400) {
1815 var msg = errorThrow;
1815 var msg = errorThrow;
1816 } else if (xhr.status === 500) {
1816 } else if (xhr.status === 500) {
1817 var msg = "An unknown error occurred while loading this notebook. " +
1817 var msg = "An unknown error occurred while loading this notebook. " +
1818 "This version can load notebook formats " +
1818 "This version can load notebook formats " +
1819 "v" + this.nbformat + " or earlier.";
1819 "v" + this.nbformat + " or earlier.";
1820 }
1820 }
1821 IPython.dialog.modal({
1821 IPython.dialog.modal({
1822 title: "Error loading notebook",
1822 title: "Error loading notebook",
1823 body : msg,
1823 body : msg,
1824 buttons : {
1824 buttons : {
1825 "OK": {}
1825 "OK": {}
1826 }
1826 }
1827 });
1827 });
1828 }
1828 }
1829
1829
1830 /********************* checkpoint-related *********************/
1830 /********************* checkpoint-related *********************/
1831
1831
1832 /**
1832 /**
1833 * Save the notebook then immediately create a checkpoint.
1833 * Save the notebook then immediately create a checkpoint.
1834 *
1834 *
1835 * @method save_checkpoint
1835 * @method save_checkpoint
1836 */
1836 */
1837 Notebook.prototype.save_checkpoint = function () {
1837 Notebook.prototype.save_checkpoint = function () {
1838 this._checkpoint_after_save = true;
1838 this._checkpoint_after_save = true;
1839 this.save_notebook();
1839 this.save_notebook();
1840 };
1840 };
1841
1841
1842 /**
1842 /**
1843 * Add a checkpoint for this notebook.
1843 * Add a checkpoint for this notebook.
1844 * for use as a callback from checkpoint creation.
1844 * for use as a callback from checkpoint creation.
1845 *
1845 *
1846 * @method add_checkpoint
1846 * @method add_checkpoint
1847 */
1847 */
1848 Notebook.prototype.add_checkpoint = function (checkpoint) {
1848 Notebook.prototype.add_checkpoint = function (checkpoint) {
1849 var found = false;
1849 var found = false;
1850 for (var i = 0; i < this.checkpoints.length; i++) {
1850 for (var i = 0; i < this.checkpoints.length; i++) {
1851 var existing = this.checkpoints[i];
1851 var existing = this.checkpoints[i];
1852 if (existing.checkpoint_id == checkpoint.checkpoint_id) {
1852 if (existing.checkpoint_id == checkpoint.checkpoint_id) {
1853 found = true;
1853 found = true;
1854 this.checkpoints[i] = checkpoint;
1854 this.checkpoints[i] = checkpoint;
1855 break;
1855 break;
1856 }
1856 }
1857 }
1857 }
1858 if (!found) {
1858 if (!found) {
1859 this.checkpoints.push(checkpoint);
1859 this.checkpoints.push(checkpoint);
1860 }
1860 }
1861 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1861 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
1862 };
1862 };
1863
1863
1864 /**
1864 /**
1865 * List checkpoints for this notebook.
1865 * List checkpoints for this notebook.
1866 *
1866 *
1867 * @method list_checkpoints
1867 * @method list_checkpoints
1868 */
1868 */
1869 Notebook.prototype.list_checkpoints = function () {
1869 Notebook.prototype.list_checkpoints = function () {
1870 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1870 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1871 $.get(url).done(
1871 $.get(url).done(
1872 $.proxy(this.list_checkpoints_success, this)
1872 $.proxy(this.list_checkpoints_success, this)
1873 ).fail(
1873 ).fail(
1874 $.proxy(this.list_checkpoints_error, this)
1874 $.proxy(this.list_checkpoints_error, this)
1875 );
1875 );
1876 };
1876 };
1877
1877
1878 /**
1878 /**
1879 * Success callback for listing checkpoints.
1879 * Success callback for listing checkpoints.
1880 *
1880 *
1881 * @method list_checkpoint_success
1881 * @method list_checkpoint_success
1882 * @param {Object} data JSON representation of a checkpoint
1882 * @param {Object} data JSON representation of a checkpoint
1883 * @param {String} status Description of response status
1883 * @param {String} status Description of response status
1884 * @param {jqXHR} xhr jQuery Ajax object
1884 * @param {jqXHR} xhr jQuery Ajax object
1885 */
1885 */
1886 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1886 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1887 var data = $.parseJSON(data);
1887 var data = $.parseJSON(data);
1888 this.checkpoints = data;
1888 this.checkpoints = data;
1889 if (data.length) {
1889 if (data.length) {
1890 this.last_checkpoint = data[data.length - 1];
1890 this.last_checkpoint = data[data.length - 1];
1891 } else {
1891 } else {
1892 this.last_checkpoint = null;
1892 this.last_checkpoint = null;
1893 }
1893 }
1894 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1894 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1895 };
1895 };
1896
1896
1897 /**
1897 /**
1898 * Failure callback for listing a checkpoint.
1898 * Failure callback for listing a checkpoint.
1899 *
1899 *
1900 * @method list_checkpoint_error
1900 * @method list_checkpoint_error
1901 * @param {jqXHR} xhr jQuery Ajax object
1901 * @param {jqXHR} xhr jQuery Ajax object
1902 * @param {String} status Description of response status
1902 * @param {String} status Description of response status
1903 * @param {String} error_msg HTTP error message
1903 * @param {String} error_msg HTTP error message
1904 */
1904 */
1905 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1905 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1906 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1906 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1907 };
1907 };
1908
1908
1909 /**
1909 /**
1910 * Create a checkpoint of this notebook on the server from the most recent save.
1910 * Create a checkpoint of this notebook on the server from the most recent save.
1911 *
1911 *
1912 * @method create_checkpoint
1912 * @method create_checkpoint
1913 */
1913 */
1914 Notebook.prototype.create_checkpoint = function () {
1914 Notebook.prototype.create_checkpoint = function () {
1915 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1915 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1916 $.post(url).done(
1916 $.post(url).done(
1917 $.proxy(this.create_checkpoint_success, this)
1917 $.proxy(this.create_checkpoint_success, this)
1918 ).fail(
1918 ).fail(
1919 $.proxy(this.create_checkpoint_error, this)
1919 $.proxy(this.create_checkpoint_error, this)
1920 );
1920 );
1921 };
1921 };
1922
1922
1923 /**
1923 /**
1924 * Success callback for creating a checkpoint.
1924 * Success callback for creating a checkpoint.
1925 *
1925 *
1926 * @method create_checkpoint_success
1926 * @method create_checkpoint_success
1927 * @param {Object} data JSON representation of a checkpoint
1927 * @param {Object} data JSON representation of a checkpoint
1928 * @param {String} status Description of response status
1928 * @param {String} status Description of response status
1929 * @param {jqXHR} xhr jQuery Ajax object
1929 * @param {jqXHR} xhr jQuery Ajax object
1930 */
1930 */
1931 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1931 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1932 var data = $.parseJSON(data);
1932 var data = $.parseJSON(data);
1933 this.add_checkpoint(data);
1933 this.add_checkpoint(data);
1934 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1934 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1935 };
1935 };
1936
1936
1937 /**
1937 /**
1938 * Failure callback for creating a checkpoint.
1938 * Failure callback for creating a checkpoint.
1939 *
1939 *
1940 * @method create_checkpoint_error
1940 * @method create_checkpoint_error
1941 * @param {jqXHR} xhr jQuery Ajax object
1941 * @param {jqXHR} xhr jQuery Ajax object
1942 * @param {String} status Description of response status
1942 * @param {String} status Description of response status
1943 * @param {String} error_msg HTTP error message
1943 * @param {String} error_msg HTTP error message
1944 */
1944 */
1945 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1945 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1946 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1946 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1947 };
1947 };
1948
1948
1949 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1949 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1950 var that = this;
1950 var that = this;
1951 var checkpoint = checkpoint || this.last_checkpoint;
1951 var checkpoint = checkpoint || this.last_checkpoint;
1952 if ( ! checkpoint ) {
1952 if ( ! checkpoint ) {
1953 console.log("restore dialog, but no checkpoint to restore to!");
1953 console.log("restore dialog, but no checkpoint to restore to!");
1954 return;
1954 return;
1955 }
1955 }
1956 var body = $('<div/>').append(
1956 var body = $('<div/>').append(
1957 $('<p/>').addClass("p-space").text(
1957 $('<p/>').addClass("p-space").text(
1958 "Are you sure you want to revert the notebook to " +
1958 "Are you sure you want to revert the notebook to " +
1959 "the latest checkpoint?"
1959 "the latest checkpoint?"
1960 ).append(
1960 ).append(
1961 $("<strong/>").text(
1961 $("<strong/>").text(
1962 " This cannot be undone."
1962 " This cannot be undone."
1963 )
1963 )
1964 )
1964 )
1965 ).append(
1965 ).append(
1966 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1966 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1967 ).append(
1967 ).append(
1968 $('<p/>').addClass("p-space").text(
1968 $('<p/>').addClass("p-space").text(
1969 Date(checkpoint.last_modified)
1969 Date(checkpoint.last_modified)
1970 ).css("text-align", "center")
1970 ).css("text-align", "center")
1971 );
1971 );
1972
1972
1973 IPython.dialog.modal({
1973 IPython.dialog.modal({
1974 title : "Revert notebook to checkpoint",
1974 title : "Revert notebook to checkpoint",
1975 body : body,
1975 body : body,
1976 buttons : {
1976 buttons : {
1977 Revert : {
1977 Revert : {
1978 class : "btn-danger",
1978 class : "btn-danger",
1979 click : function () {
1979 click : function () {
1980 that.restore_checkpoint(checkpoint.checkpoint_id);
1980 that.restore_checkpoint(checkpoint.checkpoint_id);
1981 }
1981 }
1982 },
1982 },
1983 Cancel : {}
1983 Cancel : {}
1984 }
1984 }
1985 });
1985 });
1986 }
1986 }
1987
1987
1988 /**
1988 /**
1989 * Restore the notebook to a checkpoint state.
1989 * Restore the notebook to a checkpoint state.
1990 *
1990 *
1991 * @method restore_checkpoint
1991 * @method restore_checkpoint
1992 * @param {String} checkpoint ID
1992 * @param {String} checkpoint ID
1993 */
1993 */
1994 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1994 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1995 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1995 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1996 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1996 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1997 $.post(url).done(
1997 $.post(url).done(
1998 $.proxy(this.restore_checkpoint_success, this)
1998 $.proxy(this.restore_checkpoint_success, this)
1999 ).fail(
1999 ).fail(
2000 $.proxy(this.restore_checkpoint_error, this)
2000 $.proxy(this.restore_checkpoint_error, this)
2001 );
2001 );
2002 };
2002 };
2003
2003
2004 /**
2004 /**
2005 * Success callback for restoring a notebook to a checkpoint.
2005 * Success callback for restoring a notebook to a checkpoint.
2006 *
2006 *
2007 * @method restore_checkpoint_success
2007 * @method restore_checkpoint_success
2008 * @param {Object} data (ignored, should be empty)
2008 * @param {Object} data (ignored, should be empty)
2009 * @param {String} status Description of response status
2009 * @param {String} status Description of response status
2010 * @param {jqXHR} xhr jQuery Ajax object
2010 * @param {jqXHR} xhr jQuery Ajax object
2011 */
2011 */
2012 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2012 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2013 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2013 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2014 this.load_notebook(this.notebook_id);
2014 this.load_notebook(this.notebook_id);
2015 };
2015 };
2016
2016
2017 /**
2017 /**
2018 * Failure callback for restoring a notebook to a checkpoint.
2018 * Failure callback for restoring a notebook to a checkpoint.
2019 *
2019 *
2020 * @method restore_checkpoint_error
2020 * @method restore_checkpoint_error
2021 * @param {jqXHR} xhr jQuery Ajax object
2021 * @param {jqXHR} xhr jQuery Ajax object
2022 * @param {String} status Description of response status
2022 * @param {String} status Description of response status
2023 * @param {String} error_msg HTTP error message
2023 * @param {String} error_msg HTTP error message
2024 */
2024 */
2025 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2025 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2026 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2026 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2027 };
2027 };
2028
2028
2029 /**
2029 /**
2030 * Delete a notebook checkpoint.
2030 * Delete a notebook checkpoint.
2031 *
2031 *
2032 * @method delete_checkpoint
2032 * @method delete_checkpoint
2033 * @param {String} checkpoint ID
2033 * @param {String} checkpoint ID
2034 */
2034 */
2035 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2035 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2036 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
2036 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
2037 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
2037 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
2038 $.ajax(url, {
2038 $.ajax(url, {
2039 type: 'DELETE',
2039 type: 'DELETE',
2040 success: $.proxy(this.delete_checkpoint_success, this),
2040 success: $.proxy(this.delete_checkpoint_success, this),
2041 error: $.proxy(this.delete_notebook_error,this)
2041 error: $.proxy(this.delete_notebook_error,this)
2042 });
2042 });
2043 };
2043 };
2044
2044
2045 /**
2045 /**
2046 * Success callback for deleting a notebook checkpoint
2046 * Success callback for deleting a notebook checkpoint
2047 *
2047 *
2048 * @method delete_checkpoint_success
2048 * @method delete_checkpoint_success
2049 * @param {Object} data (ignored, should be empty)
2049 * @param {Object} data (ignored, should be empty)
2050 * @param {String} status Description of response status
2050 * @param {String} status Description of response status
2051 * @param {jqXHR} xhr jQuery Ajax object
2051 * @param {jqXHR} xhr jQuery Ajax object
2052 */
2052 */
2053 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2053 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2054 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2054 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2055 this.load_notebook(this.notebook_id);
2055 this.load_notebook(this.notebook_id);
2056 };
2056 };
2057
2057
2058 /**
2058 /**
2059 * Failure callback for deleting a notebook checkpoint.
2059 * Failure callback for deleting a notebook checkpoint.
2060 *
2060 *
2061 * @method delete_checkpoint_error
2061 * @method delete_checkpoint_error
2062 * @param {jqXHR} xhr jQuery Ajax object
2062 * @param {jqXHR} xhr jQuery Ajax object
2063 * @param {String} status Description of response status
2063 * @param {String} status Description of response status
2064 * @param {String} error_msg HTTP error message
2064 * @param {String} error_msg HTTP error message
2065 */
2065 */
2066 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2066 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2067 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2067 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2068 };
2068 };
2069
2069
2070
2070
2071 IPython.Notebook = Notebook;
2071 IPython.Notebook = Notebook;
2072
2072
2073
2073
2074 return IPython;
2074 return IPython;
2075
2075
2076 }(IPython));
2076 }(IPython));
2077
2077
@@ -1,682 +1,651 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.clear_out_timeout = null;
34 this.clear_out_timeout = null;
35 if (prompt_area === undefined) {
35 if (prompt_area === undefined) {
36 this.prompt_area = true;
36 this.prompt_area = true;
37 } else {
37 } else {
38 this.prompt_area = prompt_area;
38 this.prompt_area = prompt_area;
39 }
39 }
40 this.create_elements();
40 this.create_elements();
41 this.style();
41 this.style();
42 this.bind_events();
42 this.bind_events();
43 };
43 };
44
44
45 OutputArea.prototype.create_elements = function () {
45 OutputArea.prototype.create_elements = function () {
46 this.element = $("<div/>");
46 this.element = $("<div/>");
47 this.collapse_button = $("<div/>");
47 this.collapse_button = $("<div/>");
48 this.prompt_overlay = $("<div/>");
48 this.prompt_overlay = $("<div/>");
49 this.wrapper.append(this.prompt_overlay);
49 this.wrapper.append(this.prompt_overlay);
50 this.wrapper.append(this.element);
50 this.wrapper.append(this.element);
51 this.wrapper.append(this.collapse_button);
51 this.wrapper.append(this.collapse_button);
52 };
52 };
53
53
54
54
55 OutputArea.prototype.style = function () {
55 OutputArea.prototype.style = function () {
56 this.collapse_button.hide();
56 this.collapse_button.hide();
57 this.prompt_overlay.hide();
57 this.prompt_overlay.hide();
58
58
59 this.wrapper.addClass('output_wrapper');
59 this.wrapper.addClass('output_wrapper');
60 this.element.addClass('output vbox');
60 this.element.addClass('output vbox');
61
61
62 this.collapse_button.addClass("btn output_collapsed");
62 this.collapse_button.addClass("btn output_collapsed");
63 this.collapse_button.attr('title', 'click to expand output');
63 this.collapse_button.attr('title', 'click to expand output');
64 this.collapse_button.html('. . .');
64 this.collapse_button.html('. . .');
65
65
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68
68
69 this.collapse();
69 this.collapse();
70 };
70 };
71
71
72 /**
72 /**
73 * Should the OutputArea scroll?
73 * Should the OutputArea scroll?
74 * Returns whether the height (in lines) exceeds a threshold.
74 * Returns whether the height (in lines) exceeds a threshold.
75 *
75 *
76 * @private
76 * @private
77 * @method _should_scroll
77 * @method _should_scroll
78 * @param [lines=100]{Integer}
78 * @param [lines=100]{Integer}
79 * @return {Bool}
79 * @return {Bool}
80 *
80 *
81 */
81 */
82 OutputArea.prototype._should_scroll = function (lines) {
82 OutputArea.prototype._should_scroll = function (lines) {
83 if (lines <=0 ){ return }
83 if (lines <=0 ){ return }
84 if (!lines) {
84 if (!lines) {
85 lines = 100;
85 lines = 100;
86 }
86 }
87 // line-height from http://stackoverflow.com/questions/1185151
87 // line-height from http://stackoverflow.com/questions/1185151
88 var fontSize = this.element.css('font-size');
88 var fontSize = this.element.css('font-size');
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90
90
91 return (this.element.height() > lines * lineHeight);
91 return (this.element.height() > lines * lineHeight);
92 };
92 };
93
93
94
94
95 OutputArea.prototype.bind_events = function () {
95 OutputArea.prototype.bind_events = function () {
96 var that = this;
96 var that = this;
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99
99
100 this.element.resize(function () {
100 this.element.resize(function () {
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 if ( IPython.utils.browser[0] === "Firefox" ) {
102 if ( IPython.utils.browser[0] === "Firefox" ) {
103 return;
103 return;
104 }
104 }
105 // maybe scroll output,
105 // maybe scroll output,
106 // if it's grown large enough and hasn't already been scrolled.
106 // if it's grown large enough and hasn't already been scrolled.
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 that.scroll_area();
108 that.scroll_area();
109 }
109 }
110 });
110 });
111 this.collapse_button.click(function () {
111 this.collapse_button.click(function () {
112 that.expand();
112 that.expand();
113 });
113 });
114 };
114 };
115
115
116
116
117 OutputArea.prototype.collapse = function () {
117 OutputArea.prototype.collapse = function () {
118 if (!this.collapsed) {
118 if (!this.collapsed) {
119 this.element.hide();
119 this.element.hide();
120 this.prompt_overlay.hide();
120 this.prompt_overlay.hide();
121 if (this.element.html()){
121 if (this.element.html()){
122 this.collapse_button.show();
122 this.collapse_button.show();
123 }
123 }
124 this.collapsed = true;
124 this.collapsed = true;
125 }
125 }
126 };
126 };
127
127
128
128
129 OutputArea.prototype.expand = function () {
129 OutputArea.prototype.expand = function () {
130 if (this.collapsed) {
130 if (this.collapsed) {
131 this.collapse_button.hide();
131 this.collapse_button.hide();
132 this.element.show();
132 this.element.show();
133 this.prompt_overlay.show();
133 this.prompt_overlay.show();
134 this.collapsed = false;
134 this.collapsed = false;
135 }
135 }
136 };
136 };
137
137
138
138
139 OutputArea.prototype.toggle_output = function () {
139 OutputArea.prototype.toggle_output = function () {
140 if (this.collapsed) {
140 if (this.collapsed) {
141 this.expand();
141 this.expand();
142 } else {
142 } else {
143 this.collapse();
143 this.collapse();
144 }
144 }
145 };
145 };
146
146
147
147
148 OutputArea.prototype.scroll_area = function () {
148 OutputArea.prototype.scroll_area = function () {
149 this.element.addClass('output_scroll');
149 this.element.addClass('output_scroll');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 this.scrolled = true;
151 this.scrolled = true;
152 };
152 };
153
153
154
154
155 OutputArea.prototype.unscroll_area = function () {
155 OutputArea.prototype.unscroll_area = function () {
156 this.element.removeClass('output_scroll');
156 this.element.removeClass('output_scroll');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 this.scrolled = false;
158 this.scrolled = false;
159 };
159 };
160
160
161 /**
161 /**
162 * Threshold to trigger autoscroll when the OutputArea is resized,
162 * Threshold to trigger autoscroll when the OutputArea is resized,
163 * typically when new outputs are added.
163 * typically when new outputs are added.
164 *
164 *
165 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
165 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
166 * unless it is < 0, in which case autoscroll will never be triggered
166 * unless it is < 0, in which case autoscroll will never be triggered
167 *
167 *
168 * @property auto_scroll_threshold
168 * @property auto_scroll_threshold
169 * @type Number
169 * @type Number
170 * @default 100
170 * @default 100
171 *
171 *
172 **/
172 **/
173 OutputArea.auto_scroll_threshold = 100;
173 OutputArea.auto_scroll_threshold = 100;
174
174
175
175
176 /**
176 /**
177 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
177 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
178 * shorter than this are never scrolled.
178 * shorter than this are never scrolled.
179 *
179 *
180 * @property minimum_scroll_threshold
180 * @property minimum_scroll_threshold
181 * @type Number
181 * @type Number
182 * @default 20
182 * @default 20
183 *
183 *
184 **/
184 **/
185 OutputArea.minimum_scroll_threshold = 20;
185 OutputArea.minimum_scroll_threshold = 20;
186
186
187
187
188 /**
188 /**
189 *
189 *
190 * Scroll OutputArea if height supperior than a threshold (in lines).
190 * Scroll OutputArea if height supperior than a threshold (in lines).
191 *
191 *
192 * Threshold is a maximum number of lines. If unspecified, defaults to
192 * Threshold is a maximum number of lines. If unspecified, defaults to
193 * OutputArea.minimum_scroll_threshold.
193 * OutputArea.minimum_scroll_threshold.
194 *
194 *
195 * Negative threshold will prevent the OutputArea from ever scrolling.
195 * Negative threshold will prevent the OutputArea from ever scrolling.
196 *
196 *
197 * @method scroll_if_long
197 * @method scroll_if_long
198 *
198 *
199 * @param [lines=20]{Number} Default to 20 if not set,
199 * @param [lines=20]{Number} Default to 20 if not set,
200 * behavior undefined for value of `0`.
200 * behavior undefined for value of `0`.
201 *
201 *
202 **/
202 **/
203 OutputArea.prototype.scroll_if_long = function (lines) {
203 OutputArea.prototype.scroll_if_long = function (lines) {
204 var n = lines | OutputArea.minimum_scroll_threshold;
204 var n = lines | OutputArea.minimum_scroll_threshold;
205 if(n <= 0){
205 if(n <= 0){
206 return
206 return
207 }
207 }
208
208
209 if (this._should_scroll(n)) {
209 if (this._should_scroll(n)) {
210 // only allow scrolling long-enough output
210 // only allow scrolling long-enough output
211 this.scroll_area();
211 this.scroll_area();
212 }
212 }
213 };
213 };
214
214
215
215
216 OutputArea.prototype.toggle_scroll = function () {
216 OutputArea.prototype.toggle_scroll = function () {
217 if (this.scrolled) {
217 if (this.scrolled) {
218 this.unscroll_area();
218 this.unscroll_area();
219 } else {
219 } else {
220 // only allow scrolling long-enough output
220 // only allow scrolling long-enough output
221 this.scroll_if_long();
221 this.scroll_if_long();
222 }
222 }
223 };
223 };
224
224
225
225
226 // typeset with MathJax if MathJax is available
226 // typeset with MathJax if MathJax is available
227 OutputArea.prototype.typeset = function () {
227 OutputArea.prototype.typeset = function () {
228 if (window.MathJax){
228 if (window.MathJax){
229 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
229 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
230 }
230 }
231 };
231 };
232
232
233
233
234 OutputArea.prototype.handle_output = function (msg_type, content) {
234 OutputArea.prototype.handle_output = function (msg_type, content) {
235 var json = {};
235 var json = {};
236 json.output_type = msg_type;
236 json.output_type = msg_type;
237 if (msg_type === "stream") {
237 if (msg_type === "stream") {
238 json.text = content.data;
238 json.text = content.data;
239 json.stream = content.name;
239 json.stream = content.name;
240 } else if (msg_type === "display_data") {
240 } else if (msg_type === "display_data") {
241 json = this.convert_mime_types(json, content.data);
241 json = this.convert_mime_types(json, content.data);
242 json.metadata = this.convert_mime_types({}, content.metadata);
242 json.metadata = this.convert_mime_types({}, content.metadata);
243 } else if (msg_type === "pyout") {
243 } else if (msg_type === "pyout") {
244 json.prompt_number = content.execution_count;
244 json.prompt_number = content.execution_count;
245 json = this.convert_mime_types(json, content.data);
245 json = this.convert_mime_types(json, content.data);
246 json.metadata = this.convert_mime_types({}, content.metadata);
246 json.metadata = this.convert_mime_types({}, content.metadata);
247 } else if (msg_type === "pyerr") {
247 } else if (msg_type === "pyerr") {
248 json.ename = content.ename;
248 json.ename = content.ename;
249 json.evalue = content.evalue;
249 json.evalue = content.evalue;
250 json.traceback = content.traceback;
250 json.traceback = content.traceback;
251 }
251 }
252 // append with dynamic=true
252 // append with dynamic=true
253 this.append_output(json, true);
253 this.append_output(json, true);
254 };
254 };
255
255
256
256
257 OutputArea.prototype.convert_mime_types = function (json, data) {
257 OutputArea.prototype.convert_mime_types = function (json, data) {
258 if (data === undefined) {
258 if (data === undefined) {
259 return json;
259 return json;
260 }
260 }
261 if (data['text/plain'] !== undefined) {
261 if (data['text/plain'] !== undefined) {
262 json.text = data['text/plain'];
262 json.text = data['text/plain'];
263 }
263 }
264 if (data['text/html'] !== undefined) {
264 if (data['text/html'] !== undefined) {
265 json.html = data['text/html'];
265 json.html = data['text/html'];
266 }
266 }
267 if (data['image/svg+xml'] !== undefined) {
267 if (data['image/svg+xml'] !== undefined) {
268 json.svg = data['image/svg+xml'];
268 json.svg = data['image/svg+xml'];
269 }
269 }
270 if (data['image/png'] !== undefined) {
270 if (data['image/png'] !== undefined) {
271 json.png = data['image/png'];
271 json.png = data['image/png'];
272 }
272 }
273 if (data['image/jpeg'] !== undefined) {
273 if (data['image/jpeg'] !== undefined) {
274 json.jpeg = data['image/jpeg'];
274 json.jpeg = data['image/jpeg'];
275 }
275 }
276 if (data['text/latex'] !== undefined) {
276 if (data['text/latex'] !== undefined) {
277 json.latex = data['text/latex'];
277 json.latex = data['text/latex'];
278 }
278 }
279 if (data['application/json'] !== undefined) {
279 if (data['application/json'] !== undefined) {
280 json.json = data['application/json'];
280 json.json = data['application/json'];
281 }
281 }
282 if (data['application/javascript'] !== undefined) {
282 if (data['application/javascript'] !== undefined) {
283 json.javascript = data['application/javascript'];
283 json.javascript = data['application/javascript'];
284 }
284 }
285 return json;
285 return json;
286 };
286 };
287
287
288
288
289 OutputArea.prototype.append_output = function (json, dynamic) {
289 OutputArea.prototype.append_output = function (json, dynamic) {
290 // If dynamic is true, javascript output will be eval'd.
290 // If dynamic is true, javascript output will be eval'd.
291 this.expand();
291 this.expand();
292 if (json.output_type === 'pyout') {
292 if (json.output_type === 'pyout') {
293 this.append_pyout(json, dynamic);
293 this.append_pyout(json, dynamic);
294 } else if (json.output_type === 'pyerr') {
294 } else if (json.output_type === 'pyerr') {
295 this.append_pyerr(json);
295 this.append_pyerr(json);
296 } else if (json.output_type === 'display_data') {
296 } else if (json.output_type === 'display_data') {
297 this.append_display_data(json, dynamic);
297 this.append_display_data(json, dynamic);
298 } else if (json.output_type === 'stream') {
298 } else if (json.output_type === 'stream') {
299 this.append_stream(json);
299 this.append_stream(json);
300 }
300 }
301 this.outputs.push(json);
301 this.outputs.push(json);
302 this.element.height('auto');
302 this.element.height('auto');
303 var that = this;
303 var that = this;
304 setTimeout(function(){that.element.trigger('resize');}, 100);
304 setTimeout(function(){that.element.trigger('resize');}, 100);
305 };
305 };
306
306
307
307
308 OutputArea.prototype.create_output_area = function () {
308 OutputArea.prototype.create_output_area = function () {
309 var oa = $("<div/>").addClass("output_area");
309 var oa = $("<div/>").addClass("output_area");
310 if (this.prompt_area) {
310 if (this.prompt_area) {
311 oa.append($('<div/>').addClass('prompt'));
311 oa.append($('<div/>').addClass('prompt'));
312 }
312 }
313 return oa;
313 return oa;
314 };
314 };
315
315
316 OutputArea.prototype._append_javascript_error = function (err, container) {
316 OutputArea.prototype._append_javascript_error = function (err, container) {
317 // display a message when a javascript error occurs in display output
317 // display a message when a javascript error occurs in display output
318 var msg = "Javascript error adding output!"
318 var msg = "Javascript error adding output!"
319 console.log(msg, err);
319 console.log(msg, err);
320 if ( container === undefined ) return;
320 if ( container === undefined ) return;
321 container.append(
321 container.append(
322 $('<div/>').html(msg + "<br/>" +
322 $('<div/>').html(msg + "<br/>" +
323 err.toString() +
323 err.toString() +
324 '<br/>See your browser Javascript console for more details.'
324 '<br/>See your browser Javascript console for more details.'
325 ).addClass('js-error')
325 ).addClass('js-error')
326 );
326 );
327 container.show();
327 container.show();
328 };
328 };
329
329
330 OutputArea.prototype._safe_append = function (toinsert) {
330 OutputArea.prototype._safe_append = function (toinsert) {
331 // safely append an item to the document
331 // safely append an item to the document
332 // this is an object created by user code,
332 // this is an object created by user code,
333 // and may have errors, which should not be raised
333 // and may have errors, which should not be raised
334 // under any circumstances.
334 // under any circumstances.
335 try {
335 try {
336 this.element.append(toinsert);
336 this.element.append(toinsert);
337 } catch(err) {
337 } catch(err) {
338 console.log(err);
338 console.log(err);
339 this._append_javascript_error(err, this.element);
339 this._append_javascript_error(err, this.element);
340 }
340 }
341 };
341 };
342
342
343
343
344 OutputArea.prototype.append_pyout = function (json, dynamic) {
344 OutputArea.prototype.append_pyout = function (json, dynamic) {
345 var n = json.prompt_number || ' ';
345 var n = json.prompt_number || ' ';
346 var toinsert = this.create_output_area();
346 var toinsert = this.create_output_area();
347 if (this.prompt_area) {
347 if (this.prompt_area) {
348 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
348 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
349 }
349 }
350 this.append_mime_type(json, toinsert, dynamic);
350 this.append_mime_type(json, toinsert, dynamic);
351 this._safe_append(toinsert);
351 this._safe_append(toinsert);
352 // If we just output latex, typeset it.
352 // If we just output latex, typeset it.
353 if ((json.latex !== undefined) || (json.html !== undefined)) {
353 if ((json.latex !== undefined) || (json.html !== undefined)) {
354 this.typeset();
354 this.typeset();
355 }
355 }
356 };
356 };
357
357
358
358
359 OutputArea.prototype.append_pyerr = function (json) {
359 OutputArea.prototype.append_pyerr = function (json) {
360 var tb = json.traceback;
360 var tb = json.traceback;
361 if (tb !== undefined && tb.length > 0) {
361 if (tb !== undefined && tb.length > 0) {
362 var s = '';
362 var s = '';
363 var len = tb.length;
363 var len = tb.length;
364 for (var i=0; i<len; i++) {
364 for (var i=0; i<len; i++) {
365 s = s + tb[i] + '\n';
365 s = s + tb[i] + '\n';
366 }
366 }
367 s = s + '\n';
367 s = s + '\n';
368 var toinsert = this.create_output_area();
368 var toinsert = this.create_output_area();
369 this.append_text(s, {}, toinsert);
369 this.append_text(s, {}, toinsert);
370 this._safe_append(toinsert);
370 this._safe_append(toinsert);
371 }
371 }
372 };
372 };
373
373
374
374
375 OutputArea.prototype.append_stream = function (json) {
375 OutputArea.prototype.append_stream = function (json) {
376 // temporary fix: if stream undefined (json file written prior to this patch),
376 // temporary fix: if stream undefined (json file written prior to this patch),
377 // default to most likely stdout:
377 // default to most likely stdout:
378 if (json.stream == undefined){
378 if (json.stream == undefined){
379 json.stream = 'stdout';
379 json.stream = 'stdout';
380 }
380 }
381 var text = json.text;
381 var text = json.text;
382 var subclass = "output_"+json.stream;
382 var subclass = "output_"+json.stream;
383 if (this.outputs.length > 0){
383 if (this.outputs.length > 0){
384 // have at least one output to consider
384 // have at least one output to consider
385 var last = this.outputs[this.outputs.length-1];
385 var last = this.outputs[this.outputs.length-1];
386 if (last.output_type == 'stream' && json.stream == last.stream){
386 if (last.output_type == 'stream' && json.stream == last.stream){
387 // latest output was in the same stream,
387 // latest output was in the same stream,
388 // so append directly into its pre tag
388 // so append directly into its pre tag
389 // escape ANSI & HTML specials:
389 // escape ANSI & HTML specials:
390 var pre = this.element.find('div.'+subclass).last().find('pre');
390 var pre = this.element.find('div.'+subclass).last().find('pre');
391 var html = utils.fixCarriageReturn(
391 var html = utils.fixCarriageReturn(
392 pre.html() + utils.fixConsole(text));
392 pre.html() + utils.fixConsole(text));
393 pre.html(html);
393 pre.html(html);
394 return;
394 return;
395 }
395 }
396 }
396 }
397
397
398 if (!text.replace("\r", "")) {
398 if (!text.replace("\r", "")) {
399 // text is nothing (empty string, \r, etc.)
399 // text is nothing (empty string, \r, etc.)
400 // so don't append any elements, which might add undesirable space
400 // so don't append any elements, which might add undesirable space
401 return;
401 return;
402 }
402 }
403
403
404 // If we got here, attach a new div
404 // If we got here, attach a new div
405 var toinsert = this.create_output_area();
405 var toinsert = this.create_output_area();
406 this.append_text(text, {}, toinsert, "output_stream "+subclass);
406 this.append_text(text, {}, toinsert, "output_stream "+subclass);
407 this._safe_append(toinsert);
407 this._safe_append(toinsert);
408 };
408 };
409
409
410
410
411 OutputArea.prototype.append_display_data = function (json, dynamic) {
411 OutputArea.prototype.append_display_data = function (json, dynamic) {
412 var toinsert = this.create_output_area();
412 var toinsert = this.create_output_area();
413 this.append_mime_type(json, toinsert, dynamic);
413 this.append_mime_type(json, toinsert, dynamic);
414 this._safe_append(toinsert);
414 this._safe_append(toinsert);
415 // If we just output latex, typeset it.
415 // If we just output latex, typeset it.
416 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
416 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
417 this.typeset();
417 this.typeset();
418 }
418 }
419 };
419 };
420
420
421 OutputArea.display_order = ['javascript','html','latex','svg','png','jpeg','text'];
421 OutputArea.display_order = ['javascript','html','latex','svg','png','jpeg','text'];
422
422
423 OutputArea.prototype.append_mime_type = function (json, element, dynamic) {
423 OutputArea.prototype.append_mime_type = function (json, element, dynamic) {
424 for(var type_i in OutputArea.display_order){
424 for(var type_i in OutputArea.display_order){
425 var type = OutputArea.display_order[type_i];
425 var type = OutputArea.display_order[type_i];
426 if(json[type] != undefined ){
426 if(json[type] != undefined ){
427 var md = {};
427 var md = {};
428 if (json.metadata && json.metadata[type]) {
428 if (json.metadata && json.metadata[type]) {
429 md = json.metadata[type];
429 md = json.metadata[type];
430 };
430 };
431 if(type == 'javascript'){
431 if(type == 'javascript'){
432 if (dynamic) {
432 if (dynamic) {
433 this.append_javascript(json.javascript, md, element, dynamic);
433 this.append_javascript(json.javascript, md, element, dynamic);
434 }
434 }
435 } else {
435 } else {
436 this['append_'+type](json[type], md, element);
436 this['append_'+type](json[type], md, element);
437 }
437 }
438 return;
438 return;
439 }
439 }
440 }
440 }
441 };
441 };
442
442
443
443
444 OutputArea.prototype.append_html = function (html, md, element) {
444 OutputArea.prototype.append_html = function (html, md, element) {
445 var toinsert = $("<div/>").addClass("output_subarea output_html rendered_html");
445 var toinsert = $("<div/>").addClass("output_subarea output_html rendered_html");
446 toinsert.append(html);
446 toinsert.append(html);
447 element.append(toinsert);
447 element.append(toinsert);
448 };
448 };
449
449
450
450
451 OutputArea.prototype.append_javascript = function (js, md, container) {
451 OutputArea.prototype.append_javascript = function (js, md, container) {
452 // We just eval the JS code, element appears in the local scope.
452 // We just eval the JS code, element appears in the local scope.
453 var element = $("<div/>").addClass("output_subarea");
453 var element = $("<div/>").addClass("output_subarea");
454 container.append(element);
454 container.append(element);
455 // Div for js shouldn't be drawn, as it will add empty height to the area.
455 // Div for js shouldn't be drawn, as it will add empty height to the area.
456 container.hide();
456 container.hide();
457 // If the Javascript appends content to `element` that should be drawn, then
457 // If the Javascript appends content to `element` that should be drawn, then
458 // it must also call `container.show()`.
458 // it must also call `container.show()`.
459 try {
459 try {
460 eval(js);
460 eval(js);
461 } catch(err) {
461 } catch(err) {
462 this._append_javascript_error(err, container);
462 this._append_javascript_error(err, container);
463 }
463 }
464 };
464 };
465
465
466
466
467 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
467 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
468 var toinsert = $("<div/>").addClass("output_subarea output_text");
468 var toinsert = $("<div/>").addClass("output_subarea output_text");
469 // escape ANSI & HTML specials in plaintext:
469 // escape ANSI & HTML specials in plaintext:
470 data = utils.fixConsole(data);
470 data = utils.fixConsole(data);
471 data = utils.fixCarriageReturn(data);
471 data = utils.fixCarriageReturn(data);
472 data = utils.autoLinkUrls(data);
472 data = utils.autoLinkUrls(data);
473 if (extra_class){
473 if (extra_class){
474 toinsert.addClass(extra_class);
474 toinsert.addClass(extra_class);
475 }
475 }
476 toinsert.append($("<pre/>").html(data));
476 toinsert.append($("<pre/>").html(data));
477 element.append(toinsert);
477 element.append(toinsert);
478 };
478 };
479
479
480
480
481 OutputArea.prototype.append_svg = function (svg, md, element) {
481 OutputArea.prototype.append_svg = function (svg, md, element) {
482 var toinsert = $("<div/>").addClass("output_subarea output_svg");
482 var toinsert = $("<div/>").addClass("output_subarea output_svg");
483 toinsert.append(svg);
483 toinsert.append(svg);
484 element.append(toinsert);
484 element.append(toinsert);
485 };
485 };
486
486
487
487
488 OutputArea.prototype._dblclick_to_reset_size = function (img) {
488 OutputArea.prototype._dblclick_to_reset_size = function (img) {
489 // schedule wrapping image in resizable after a delay,
489 // schedule wrapping image in resizable after a delay,
490 // so we don't end up calling resize on a zero-size object
490 // so we don't end up calling resize on a zero-size object
491 var that = this;
491 var that = this;
492 setTimeout(function () {
492 setTimeout(function () {
493 var h0 = img.height();
493 var h0 = img.height();
494 var w0 = img.width();
494 var w0 = img.width();
495 if (!(h0 && w0)) {
495 if (!(h0 && w0)) {
496 // zero size, schedule another timeout
496 // zero size, schedule another timeout
497 that._dblclick_to_reset_size(img);
497 that._dblclick_to_reset_size(img);
498 return;
498 return;
499 }
499 }
500 img.resizable({
500 img.resizable({
501 aspectRatio: true,
501 aspectRatio: true,
502 autoHide: true
502 autoHide: true
503 });
503 });
504 img.dblclick(function () {
504 img.dblclick(function () {
505 // resize wrapper & image together for some reason:
505 // resize wrapper & image together for some reason:
506 img.parent().height(h0);
506 img.parent().height(h0);
507 img.height(h0);
507 img.height(h0);
508 img.parent().width(w0);
508 img.parent().width(w0);
509 img.width(w0);
509 img.width(w0);
510 });
510 });
511 }, 250);
511 }, 250);
512 };
512 };
513
513
514
514
515 OutputArea.prototype.append_png = function (png, md, element) {
515 OutputArea.prototype.append_png = function (png, md, element) {
516 var toinsert = $("<div/>").addClass("output_subarea output_png");
516 var toinsert = $("<div/>").addClass("output_subarea output_png");
517 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
517 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
518 if (md['height']) {
518 if (md['height']) {
519 img.attr('height', md['height']);
519 img.attr('height', md['height']);
520 }
520 }
521 if (md['width']) {
521 if (md['width']) {
522 img.attr('width', md['width']);
522 img.attr('width', md['width']);
523 }
523 }
524 this._dblclick_to_reset_size(img);
524 this._dblclick_to_reset_size(img);
525 toinsert.append(img);
525 toinsert.append(img);
526 element.append(toinsert);
526 element.append(toinsert);
527 };
527 };
528
528
529
529
530 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
530 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
531 var toinsert = $("<div/>").addClass("output_subarea output_jpeg");
531 var toinsert = $("<div/>").addClass("output_subarea output_jpeg");
532 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
532 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
533 if (md['height']) {
533 if (md['height']) {
534 img.attr('height', md['height']);
534 img.attr('height', md['height']);
535 }
535 }
536 if (md['width']) {
536 if (md['width']) {
537 img.attr('width', md['width']);
537 img.attr('width', md['width']);
538 }
538 }
539 this._dblclick_to_reset_size(img);
539 this._dblclick_to_reset_size(img);
540 toinsert.append(img);
540 toinsert.append(img);
541 element.append(toinsert);
541 element.append(toinsert);
542 };
542 };
543
543
544
544
545 OutputArea.prototype.append_latex = function (latex, md, element) {
545 OutputArea.prototype.append_latex = function (latex, md, element) {
546 // This method cannot do the typesetting because the latex first has to
546 // This method cannot do the typesetting because the latex first has to
547 // be on the page.
547 // be on the page.
548 var toinsert = $("<div/>").addClass("output_subarea output_latex");
548 var toinsert = $("<div/>").addClass("output_subarea output_latex");
549 toinsert.append(latex);
549 toinsert.append(latex);
550 element.append(toinsert);
550 element.append(toinsert);
551 };
551 };
552
552
553 OutputArea.prototype.append_raw_input = function (content) {
553 OutputArea.prototype.append_raw_input = function (content) {
554 var that = this;
554 var that = this;
555 this.expand();
555 this.expand();
556 var area = this.create_output_area();
556 var area = this.create_output_area();
557
557
558 // disable any other raw_inputs, if they are left around
558 // disable any other raw_inputs, if they are left around
559 $("div.output_subarea.raw_input").remove();
559 $("div.output_subarea.raw_input").remove();
560
560
561 area.append(
561 area.append(
562 $("<div/>")
562 $("<div/>")
563 .addClass("box-flex1 output_subarea raw_input")
563 .addClass("box-flex1 output_subarea raw_input")
564 .append(
564 .append(
565 $("<span/>")
565 $("<span/>")
566 .addClass("input_prompt")
566 .addClass("input_prompt")
567 .text(content.prompt)
567 .text(content.prompt)
568 )
568 )
569 .append(
569 .append(
570 $("<input/>")
570 $("<input/>")
571 .addClass("raw_input")
571 .addClass("raw_input")
572 .attr('type', 'text')
572 .attr('type', 'text')
573 .attr("size", 47)
573 .attr("size", 47)
574 .keydown(function (event, ui) {
574 .keydown(function (event, ui) {
575 // make sure we submit on enter,
575 // make sure we submit on enter,
576 // and don't re-execute the *cell* on shift-enter
576 // and don't re-execute the *cell* on shift-enter
577 if (event.which === utils.keycodes.ENTER) {
577 if (event.which === utils.keycodes.ENTER) {
578 that._submit_raw_input();
578 that._submit_raw_input();
579 return false;
579 return false;
580 }
580 }
581 })
581 })
582 )
582 )
583 );
583 );
584 this.element.append(area);
584 this.element.append(area);
585 // weirdly need double-focus now,
585 // weirdly need double-focus now,
586 // otherwise only the cell will be focused
586 // otherwise only the cell will be focused
587 area.find("input.raw_input").focus().focus();
587 area.find("input.raw_input").focus().focus();
588 }
588 }
589 OutputArea.prototype._submit_raw_input = function (evt) {
589 OutputArea.prototype._submit_raw_input = function (evt) {
590 var container = this.element.find("div.raw_input");
590 var container = this.element.find("div.raw_input");
591 var theprompt = container.find("span.input_prompt");
591 var theprompt = container.find("span.input_prompt");
592 var theinput = container.find("input.raw_input");
592 var theinput = container.find("input.raw_input");
593 var value = theinput.val();
593 var value = theinput.val();
594 var content = {
594 var content = {
595 output_type : 'stream',
595 output_type : 'stream',
596 name : 'stdout',
596 name : 'stdout',
597 text : theprompt.text() + value + '\n'
597 text : theprompt.text() + value + '\n'
598 }
598 }
599 // remove form container
599 // remove form container
600 container.parent().remove();
600 container.parent().remove();
601 // replace with plaintext version in stdout
601 // replace with plaintext version in stdout
602 this.append_output(content, false);
602 this.append_output(content, false);
603 $([IPython.events]).trigger('send_input_reply.Kernel', value);
603 $([IPython.events]).trigger('send_input_reply.Kernel', value);
604 }
604 }
605
605
606
606
607 OutputArea.prototype.handle_clear_output = function (content) {
607 OutputArea.prototype.handle_clear_output = function (content) {
608 this.clear_output(content.stdout, content.stderr, content.other);
608 this.clear_output();
609 };
609 };
610
610
611
611
612 OutputArea.prototype.clear_output = function (stdout, stderr, other) {
612 OutputArea.prototype.clear_output = function() {
613 var output_div = this.element;
614
613
615 // Fix the output div's height
614 // Fix the output div's height
616 var height = output_div.height();
615 var height = this.element.height();
617 output_div.height(height);
616 this.element.height(height);
618
617
619 if (stdout && stderr && other){
620 // clear all, no need for logic
618 // clear all, no need for logic
621 output_div.html("");
619 this.element.html("");
622 this.outputs = [];
620 this.outputs = [];
623 this.unscroll_area();
621 this.unscroll_area();
624 return;
622 return;
625 }
626 // remove html output
627 // each output_subarea that has an identifying class is in an output_area
628 // which is the element to be removed.
629 if (stdout) {
630 output_div.find("div.output_stdout").parent().remove();
631 }
632 if (stderr) {
633 output_div.find("div.output_stderr").parent().remove();
634 }
635 if (other) {
636 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
637 }
638 this.unscroll_area();
639
640 // remove cleared outputs from JSON list:
641 for (var i = this.outputs.length - 1; i >= 0; i--) {
642 var out = this.outputs[i];
643 var output_type = out.output_type;
644 if (output_type == "display_data" && other) {
645 this.outputs.splice(i,1);
646 } else if (output_type == "stream") {
647 if (stdout && out.stream == "stdout") {
648 this.outputs.splice(i,1);
649 } else if (stderr && out.stream == "stderr") {
650 this.outputs.splice(i,1);
651 }
652 }
653 }
654 };
623 };
655
624
656
625
657 // JSON serialization
626 // JSON serialization
658
627
659 OutputArea.prototype.fromJSON = function (outputs) {
628 OutputArea.prototype.fromJSON = function (outputs) {
660 var len = outputs.length;
629 var len = outputs.length;
661 for (var i=0; i<len; i++) {
630 for (var i=0; i<len; i++) {
662 // append with dynamic=false.
631 // append with dynamic=false.
663 this.append_output(outputs[i], false);
632 this.append_output(outputs[i], false);
664 }
633 }
665 };
634 };
666
635
667
636
668 OutputArea.prototype.toJSON = function () {
637 OutputArea.prototype.toJSON = function () {
669 var outputs = [];
638 var outputs = [];
670 var len = this.outputs.length;
639 var len = this.outputs.length;
671 for (var i=0; i<len; i++) {
640 for (var i=0; i<len; i++) {
672 outputs[i] = this.outputs[i];
641 outputs[i] = this.outputs[i];
673 }
642 }
674 return outputs;
643 return outputs;
675 };
644 };
676
645
677
646
678 IPython.OutputArea = OutputArea;
647 IPython.OutputArea = OutputArea;
679
648
680 return IPython;
649 return IPython;
681
650
682 }(IPython));
651 }(IPython));
@@ -1,602 +1,599 b''
1 """A ZMQ-based subclass of InteractiveShell.
1 """A ZMQ-based subclass of InteractiveShell.
2
2
3 This code is meant to ease the refactoring of the base InteractiveShell into
3 This code is meant to ease the refactoring of the base InteractiveShell into
4 something with a cleaner architecture for 2-process use, without actually
4 something with a cleaner architecture for 2-process use, without actually
5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 we subclass and override what we want to fix. Once this is working well, we
6 we subclass and override what we want to fix. Once this is working well, we
7 can go back to the base class and refactor the code for a cleaner inheritance
7 can go back to the base class and refactor the code for a cleaner inheritance
8 implementation that doesn't rely on so much monkeypatching.
8 implementation that doesn't rely on so much monkeypatching.
9
9
10 But this lets us maintain a fully working IPython as we develop the new
10 But this lets us maintain a fully working IPython as we develop the new
11 machinery. This should thus be thought of as scaffolding.
11 machinery. This should thus be thought of as scaffolding.
12 """
12 """
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib
18 # Stdlib
19 import os
19 import os
20 import sys
20 import sys
21 import time
21 import time
22
22
23 # System library imports
23 # System library imports
24 from zmq.eventloop import ioloop
24 from zmq.eventloop import ioloop
25
25
26 # Our own
26 # Our own
27 from IPython.core.interactiveshell import (
27 from IPython.core.interactiveshell import (
28 InteractiveShell, InteractiveShellABC
28 InteractiveShell, InteractiveShellABC
29 )
29 )
30 from IPython.core import page
30 from IPython.core import page
31 from IPython.core.autocall import ZMQExitAutocall
31 from IPython.core.autocall import ZMQExitAutocall
32 from IPython.core.displaypub import DisplayPublisher
32 from IPython.core.displaypub import DisplayPublisher
33 from IPython.core.error import UsageError
33 from IPython.core.error import UsageError
34 from IPython.core.magics import MacroToEdit, CodeMagics
34 from IPython.core.magics import MacroToEdit, CodeMagics
35 from IPython.core.magic import magics_class, line_magic, Magics
35 from IPython.core.magic import magics_class, line_magic, Magics
36 from IPython.core.payloadpage import install_payload_page
36 from IPython.core.payloadpage import install_payload_page
37 from IPython.display import display, Javascript
37 from IPython.display import display, Javascript
38 from IPython.kernel.inprocess.socket import SocketABC
38 from IPython.kernel.inprocess.socket import SocketABC
39 from IPython.kernel import (
39 from IPython.kernel import (
40 get_connection_file, get_connection_info, connect_qtconsole
40 get_connection_file, get_connection_info, connect_qtconsole
41 )
41 )
42 from IPython.testing.skipdoctest import skip_doctest
42 from IPython.testing.skipdoctest import skip_doctest
43 from IPython.utils import openpy
43 from IPython.utils import openpy
44 from IPython.utils.jsonutil import json_clean, encode_images
44 from IPython.utils.jsonutil import json_clean, encode_images
45 from IPython.utils.process import arg_split
45 from IPython.utils.process import arg_split
46 from IPython.utils import py3compat
46 from IPython.utils import py3compat
47 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes
47 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes
48 from IPython.utils.warn import error
48 from IPython.utils.warn import error
49 from IPython.kernel.zmq.displayhook import ZMQShellDisplayHook
49 from IPython.kernel.zmq.displayhook import ZMQShellDisplayHook
50 from IPython.kernel.zmq.datapub import ZMQDataPublisher
50 from IPython.kernel.zmq.datapub import ZMQDataPublisher
51 from IPython.kernel.zmq.session import extract_header
51 from IPython.kernel.zmq.session import extract_header
52 from session import Session
52 from session import Session
53
53
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55 # Functions and classes
55 # Functions and classes
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57
57
58 class ZMQDisplayPublisher(DisplayPublisher):
58 class ZMQDisplayPublisher(DisplayPublisher):
59 """A display publisher that publishes data using a ZeroMQ PUB socket."""
59 """A display publisher that publishes data using a ZeroMQ PUB socket."""
60
60
61 session = Instance(Session)
61 session = Instance(Session)
62 pub_socket = Instance(SocketABC)
62 pub_socket = Instance(SocketABC)
63 parent_header = Dict({})
63 parent_header = Dict({})
64 topic = CBytes(b'display_data')
64 topic = CBytes(b'display_data')
65
65
66 def set_parent(self, parent):
66 def set_parent(self, parent):
67 """Set the parent for outbound messages."""
67 """Set the parent for outbound messages."""
68 self.parent_header = extract_header(parent)
68 self.parent_header = extract_header(parent)
69
69
70 def _flush_streams(self):
70 def _flush_streams(self):
71 """flush IO Streams prior to display"""
71 """flush IO Streams prior to display"""
72 sys.stdout.flush()
72 sys.stdout.flush()
73 sys.stderr.flush()
73 sys.stderr.flush()
74
74
75 def publish(self, source, data, metadata=None):
75 def publish(self, source, data, metadata=None):
76 self._flush_streams()
76 self._flush_streams()
77 if metadata is None:
77 if metadata is None:
78 metadata = {}
78 metadata = {}
79 self._validate_data(source, data, metadata)
79 self._validate_data(source, data, metadata)
80 content = {}
80 content = {}
81 content['source'] = source
81 content['source'] = source
82 content['data'] = encode_images(data)
82 content['data'] = encode_images(data)
83 content['metadata'] = metadata
83 content['metadata'] = metadata
84 self.session.send(
84 self.session.send(
85 self.pub_socket, u'display_data', json_clean(content),
85 self.pub_socket, u'display_data', json_clean(content),
86 parent=self.parent_header, ident=self.topic,
86 parent=self.parent_header, ident=self.topic,
87 )
87 )
88
88
89 def clear_output(self, stdout=True, stderr=True, other=True):
89 def clear_output(self):
90 content = dict(stdout=stdout, stderr=stderr, other=other)
90 content = {}
91
91
92 if stdout:
93 print('\r', file=sys.stdout, end='')
92 print('\r', file=sys.stdout, end='')
94 if stderr:
95 print('\r', file=sys.stderr, end='')
93 print('\r', file=sys.stderr, end='')
96
97 self._flush_streams()
94 self._flush_streams()
98
95
99 self.session.send(
96 self.session.send(
100 self.pub_socket, u'clear_output', content,
97 self.pub_socket, u'clear_output', content,
101 parent=self.parent_header, ident=self.topic,
98 parent=self.parent_header, ident=self.topic,
102 )
99 )
103
100
104 @magics_class
101 @magics_class
105 class KernelMagics(Magics):
102 class KernelMagics(Magics):
106 #------------------------------------------------------------------------
103 #------------------------------------------------------------------------
107 # Magic overrides
104 # Magic overrides
108 #------------------------------------------------------------------------
105 #------------------------------------------------------------------------
109 # Once the base class stops inheriting from magic, this code needs to be
106 # Once the base class stops inheriting from magic, this code needs to be
110 # moved into a separate machinery as well. For now, at least isolate here
107 # moved into a separate machinery as well. For now, at least isolate here
111 # the magics which this class needs to implement differently from the base
108 # the magics which this class needs to implement differently from the base
112 # class, or that are unique to it.
109 # class, or that are unique to it.
113
110
114 @line_magic
111 @line_magic
115 def doctest_mode(self, parameter_s=''):
112 def doctest_mode(self, parameter_s=''):
116 """Toggle doctest mode on and off.
113 """Toggle doctest mode on and off.
117
114
118 This mode is intended to make IPython behave as much as possible like a
115 This mode is intended to make IPython behave as much as possible like a
119 plain Python shell, from the perspective of how its prompts, exceptions
116 plain Python shell, from the perspective of how its prompts, exceptions
120 and output look. This makes it easy to copy and paste parts of a
117 and output look. This makes it easy to copy and paste parts of a
121 session into doctests. It does so by:
118 session into doctests. It does so by:
122
119
123 - Changing the prompts to the classic ``>>>`` ones.
120 - Changing the prompts to the classic ``>>>`` ones.
124 - Changing the exception reporting mode to 'Plain'.
121 - Changing the exception reporting mode to 'Plain'.
125 - Disabling pretty-printing of output.
122 - Disabling pretty-printing of output.
126
123
127 Note that IPython also supports the pasting of code snippets that have
124 Note that IPython also supports the pasting of code snippets that have
128 leading '>>>' and '...' prompts in them. This means that you can paste
125 leading '>>>' and '...' prompts in them. This means that you can paste
129 doctests from files or docstrings (even if they have leading
126 doctests from files or docstrings (even if they have leading
130 whitespace), and the code will execute correctly. You can then use
127 whitespace), and the code will execute correctly. You can then use
131 '%history -t' to see the translated history; this will give you the
128 '%history -t' to see the translated history; this will give you the
132 input after removal of all the leading prompts and whitespace, which
129 input after removal of all the leading prompts and whitespace, which
133 can be pasted back into an editor.
130 can be pasted back into an editor.
134
131
135 With these features, you can switch into this mode easily whenever you
132 With these features, you can switch into this mode easily whenever you
136 need to do testing and changes to doctests, without having to leave
133 need to do testing and changes to doctests, without having to leave
137 your existing IPython session.
134 your existing IPython session.
138 """
135 """
139
136
140 from IPython.utils.ipstruct import Struct
137 from IPython.utils.ipstruct import Struct
141
138
142 # Shorthands
139 # Shorthands
143 shell = self.shell
140 shell = self.shell
144 disp_formatter = self.shell.display_formatter
141 disp_formatter = self.shell.display_formatter
145 ptformatter = disp_formatter.formatters['text/plain']
142 ptformatter = disp_formatter.formatters['text/plain']
146 # dstore is a data store kept in the instance metadata bag to track any
143 # dstore is a data store kept in the instance metadata bag to track any
147 # changes we make, so we can undo them later.
144 # changes we make, so we can undo them later.
148 dstore = shell.meta.setdefault('doctest_mode', Struct())
145 dstore = shell.meta.setdefault('doctest_mode', Struct())
149 save_dstore = dstore.setdefault
146 save_dstore = dstore.setdefault
150
147
151 # save a few values we'll need to recover later
148 # save a few values we'll need to recover later
152 mode = save_dstore('mode', False)
149 mode = save_dstore('mode', False)
153 save_dstore('rc_pprint', ptformatter.pprint)
150 save_dstore('rc_pprint', ptformatter.pprint)
154 save_dstore('rc_active_types',disp_formatter.active_types)
151 save_dstore('rc_active_types',disp_formatter.active_types)
155 save_dstore('xmode', shell.InteractiveTB.mode)
152 save_dstore('xmode', shell.InteractiveTB.mode)
156
153
157 if mode == False:
154 if mode == False:
158 # turn on
155 # turn on
159 ptformatter.pprint = False
156 ptformatter.pprint = False
160 disp_formatter.active_types = ['text/plain']
157 disp_formatter.active_types = ['text/plain']
161 shell.magic('xmode Plain')
158 shell.magic('xmode Plain')
162 else:
159 else:
163 # turn off
160 # turn off
164 ptformatter.pprint = dstore.rc_pprint
161 ptformatter.pprint = dstore.rc_pprint
165 disp_formatter.active_types = dstore.rc_active_types
162 disp_formatter.active_types = dstore.rc_active_types
166 shell.magic("xmode " + dstore.xmode)
163 shell.magic("xmode " + dstore.xmode)
167
164
168 # Store new mode and inform on console
165 # Store new mode and inform on console
169 dstore.mode = bool(1-int(mode))
166 dstore.mode = bool(1-int(mode))
170 mode_label = ['OFF','ON'][dstore.mode]
167 mode_label = ['OFF','ON'][dstore.mode]
171 print('Doctest mode is:', mode_label)
168 print('Doctest mode is:', mode_label)
172
169
173 # Send the payload back so that clients can modify their prompt display
170 # Send the payload back so that clients can modify their prompt display
174 payload = dict(
171 payload = dict(
175 source='doctest_mode',
172 source='doctest_mode',
176 mode=dstore.mode)
173 mode=dstore.mode)
177 shell.payload_manager.write_payload(payload)
174 shell.payload_manager.write_payload(payload)
178
175
179
176
180 _find_edit_target = CodeMagics._find_edit_target
177 _find_edit_target = CodeMagics._find_edit_target
181
178
182 @skip_doctest
179 @skip_doctest
183 @line_magic
180 @line_magic
184 def edit(self, parameter_s='', last_call=['','']):
181 def edit(self, parameter_s='', last_call=['','']):
185 """Bring up an editor and execute the resulting code.
182 """Bring up an editor and execute the resulting code.
186
183
187 Usage:
184 Usage:
188 %edit [options] [args]
185 %edit [options] [args]
189
186
190 %edit runs an external text editor. You will need to set the command for
187 %edit runs an external text editor. You will need to set the command for
191 this editor via the ``TerminalInteractiveShell.editor`` option in your
188 this editor via the ``TerminalInteractiveShell.editor`` option in your
192 configuration file before it will work.
189 configuration file before it will work.
193
190
194 This command allows you to conveniently edit multi-line code right in
191 This command allows you to conveniently edit multi-line code right in
195 your IPython session.
192 your IPython session.
196
193
197 If called without arguments, %edit opens up an empty editor with a
194 If called without arguments, %edit opens up an empty editor with a
198 temporary file and will execute the contents of this file when you
195 temporary file and will execute the contents of this file when you
199 close it (don't forget to save it!).
196 close it (don't forget to save it!).
200
197
201
198
202 Options:
199 Options:
203
200
204 -n <number>: open the editor at a specified line number. By default,
201 -n <number>: open the editor at a specified line number. By default,
205 the IPython editor hook uses the unix syntax 'editor +N filename', but
202 the IPython editor hook uses the unix syntax 'editor +N filename', but
206 you can configure this by providing your own modified hook if your
203 you can configure this by providing your own modified hook if your
207 favorite editor supports line-number specifications with a different
204 favorite editor supports line-number specifications with a different
208 syntax.
205 syntax.
209
206
210 -p: this will call the editor with the same data as the previous time
207 -p: this will call the editor with the same data as the previous time
211 it was used, regardless of how long ago (in your current session) it
208 it was used, regardless of how long ago (in your current session) it
212 was.
209 was.
213
210
214 -r: use 'raw' input. This option only applies to input taken from the
211 -r: use 'raw' input. This option only applies to input taken from the
215 user's history. By default, the 'processed' history is used, so that
212 user's history. By default, the 'processed' history is used, so that
216 magics are loaded in their transformed version to valid Python. If
213 magics are loaded in their transformed version to valid Python. If
217 this option is given, the raw input as typed as the command line is
214 this option is given, the raw input as typed as the command line is
218 used instead. When you exit the editor, it will be executed by
215 used instead. When you exit the editor, it will be executed by
219 IPython's own processor.
216 IPython's own processor.
220
217
221 -x: do not execute the edited code immediately upon exit. This is
218 -x: do not execute the edited code immediately upon exit. This is
222 mainly useful if you are editing programs which need to be called with
219 mainly useful if you are editing programs which need to be called with
223 command line arguments, which you can then do using %run.
220 command line arguments, which you can then do using %run.
224
221
225
222
226 Arguments:
223 Arguments:
227
224
228 If arguments are given, the following possibilites exist:
225 If arguments are given, the following possibilites exist:
229
226
230 - The arguments are numbers or pairs of colon-separated numbers (like
227 - The arguments are numbers or pairs of colon-separated numbers (like
231 1 4:8 9). These are interpreted as lines of previous input to be
228 1 4:8 9). These are interpreted as lines of previous input to be
232 loaded into the editor. The syntax is the same of the %macro command.
229 loaded into the editor. The syntax is the same of the %macro command.
233
230
234 - If the argument doesn't start with a number, it is evaluated as a
231 - If the argument doesn't start with a number, it is evaluated as a
235 variable and its contents loaded into the editor. You can thus edit
232 variable and its contents loaded into the editor. You can thus edit
236 any string which contains python code (including the result of
233 any string which contains python code (including the result of
237 previous edits).
234 previous edits).
238
235
239 - If the argument is the name of an object (other than a string),
236 - If the argument is the name of an object (other than a string),
240 IPython will try to locate the file where it was defined and open the
237 IPython will try to locate the file where it was defined and open the
241 editor at the point where it is defined. You can use `%edit function`
238 editor at the point where it is defined. You can use `%edit function`
242 to load an editor exactly at the point where 'function' is defined,
239 to load an editor exactly at the point where 'function' is defined,
243 edit it and have the file be executed automatically.
240 edit it and have the file be executed automatically.
244
241
245 If the object is a macro (see %macro for details), this opens up your
242 If the object is a macro (see %macro for details), this opens up your
246 specified editor with a temporary file containing the macro's data.
243 specified editor with a temporary file containing the macro's data.
247 Upon exit, the macro is reloaded with the contents of the file.
244 Upon exit, the macro is reloaded with the contents of the file.
248
245
249 Note: opening at an exact line is only supported under Unix, and some
246 Note: opening at an exact line is only supported under Unix, and some
250 editors (like kedit and gedit up to Gnome 2.8) do not understand the
247 editors (like kedit and gedit up to Gnome 2.8) do not understand the
251 '+NUMBER' parameter necessary for this feature. Good editors like
248 '+NUMBER' parameter necessary for this feature. Good editors like
252 (X)Emacs, vi, jed, pico and joe all do.
249 (X)Emacs, vi, jed, pico and joe all do.
253
250
254 - If the argument is not found as a variable, IPython will look for a
251 - If the argument is not found as a variable, IPython will look for a
255 file with that name (adding .py if necessary) and load it into the
252 file with that name (adding .py if necessary) and load it into the
256 editor. It will execute its contents with execfile() when you exit,
253 editor. It will execute its contents with execfile() when you exit,
257 loading any code in the file into your interactive namespace.
254 loading any code in the file into your interactive namespace.
258
255
259 After executing your code, %edit will return as output the code you
256 After executing your code, %edit will return as output the code you
260 typed in the editor (except when it was an existing file). This way
257 typed in the editor (except when it was an existing file). This way
261 you can reload the code in further invocations of %edit as a variable,
258 you can reload the code in further invocations of %edit as a variable,
262 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
259 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
263 the output.
260 the output.
264
261
265 Note that %edit is also available through the alias %ed.
262 Note that %edit is also available through the alias %ed.
266
263
267 This is an example of creating a simple function inside the editor and
264 This is an example of creating a simple function inside the editor and
268 then modifying it. First, start up the editor:
265 then modifying it. First, start up the editor:
269
266
270 In [1]: ed
267 In [1]: ed
271 Editing... done. Executing edited code...
268 Editing... done. Executing edited code...
272 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
269 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
273
270
274 We can then call the function foo():
271 We can then call the function foo():
275
272
276 In [2]: foo()
273 In [2]: foo()
277 foo() was defined in an editing session
274 foo() was defined in an editing session
278
275
279 Now we edit foo. IPython automatically loads the editor with the
276 Now we edit foo. IPython automatically loads the editor with the
280 (temporary) file where foo() was previously defined:
277 (temporary) file where foo() was previously defined:
281
278
282 In [3]: ed foo
279 In [3]: ed foo
283 Editing... done. Executing edited code...
280 Editing... done. Executing edited code...
284
281
285 And if we call foo() again we get the modified version:
282 And if we call foo() again we get the modified version:
286
283
287 In [4]: foo()
284 In [4]: foo()
288 foo() has now been changed!
285 foo() has now been changed!
289
286
290 Here is an example of how to edit a code snippet successive
287 Here is an example of how to edit a code snippet successive
291 times. First we call the editor:
288 times. First we call the editor:
292
289
293 In [5]: ed
290 In [5]: ed
294 Editing... done. Executing edited code...
291 Editing... done. Executing edited code...
295 hello
292 hello
296 Out[5]: "print 'hello'n"
293 Out[5]: "print 'hello'n"
297
294
298 Now we call it again with the previous output (stored in _):
295 Now we call it again with the previous output (stored in _):
299
296
300 In [6]: ed _
297 In [6]: ed _
301 Editing... done. Executing edited code...
298 Editing... done. Executing edited code...
302 hello world
299 hello world
303 Out[6]: "print 'hello world'n"
300 Out[6]: "print 'hello world'n"
304
301
305 Now we call it with the output #8 (stored in _8, also as Out[8]):
302 Now we call it with the output #8 (stored in _8, also as Out[8]):
306
303
307 In [7]: ed _8
304 In [7]: ed _8
308 Editing... done. Executing edited code...
305 Editing... done. Executing edited code...
309 hello again
306 hello again
310 Out[7]: "print 'hello again'n"
307 Out[7]: "print 'hello again'n"
311 """
308 """
312
309
313 opts,args = self.parse_options(parameter_s,'prn:')
310 opts,args = self.parse_options(parameter_s,'prn:')
314
311
315 try:
312 try:
316 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
313 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
317 except MacroToEdit as e:
314 except MacroToEdit as e:
318 # TODO: Implement macro editing over 2 processes.
315 # TODO: Implement macro editing over 2 processes.
319 print("Macro editing not yet implemented in 2-process model.")
316 print("Macro editing not yet implemented in 2-process model.")
320 return
317 return
321
318
322 # Make sure we send to the client an absolute path, in case the working
319 # Make sure we send to the client an absolute path, in case the working
323 # directory of client and kernel don't match
320 # directory of client and kernel don't match
324 filename = os.path.abspath(filename)
321 filename = os.path.abspath(filename)
325
322
326 payload = {
323 payload = {
327 'source' : 'edit_magic',
324 'source' : 'edit_magic',
328 'filename' : filename,
325 'filename' : filename,
329 'line_number' : lineno
326 'line_number' : lineno
330 }
327 }
331 self.shell.payload_manager.write_payload(payload)
328 self.shell.payload_manager.write_payload(payload)
332
329
333 # A few magics that are adapted to the specifics of using pexpect and a
330 # A few magics that are adapted to the specifics of using pexpect and a
334 # remote terminal
331 # remote terminal
335
332
336 @line_magic
333 @line_magic
337 def clear(self, arg_s):
334 def clear(self, arg_s):
338 """Clear the terminal."""
335 """Clear the terminal."""
339 if os.name == 'posix':
336 if os.name == 'posix':
340 self.shell.system("clear")
337 self.shell.system("clear")
341 else:
338 else:
342 self.shell.system("cls")
339 self.shell.system("cls")
343
340
344 if os.name == 'nt':
341 if os.name == 'nt':
345 # This is the usual name in windows
342 # This is the usual name in windows
346 cls = line_magic('cls')(clear)
343 cls = line_magic('cls')(clear)
347
344
348 # Terminal pagers won't work over pexpect, but we do have our own pager
345 # Terminal pagers won't work over pexpect, but we do have our own pager
349
346
350 @line_magic
347 @line_magic
351 def less(self, arg_s):
348 def less(self, arg_s):
352 """Show a file through the pager.
349 """Show a file through the pager.
353
350
354 Files ending in .py are syntax-highlighted."""
351 Files ending in .py are syntax-highlighted."""
355 if not arg_s:
352 if not arg_s:
356 raise UsageError('Missing filename.')
353 raise UsageError('Missing filename.')
357
354
358 cont = open(arg_s).read()
355 cont = open(arg_s).read()
359 if arg_s.endswith('.py'):
356 if arg_s.endswith('.py'):
360 cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False))
357 cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False))
361 else:
358 else:
362 cont = open(arg_s).read()
359 cont = open(arg_s).read()
363 page.page(cont)
360 page.page(cont)
364
361
365 more = line_magic('more')(less)
362 more = line_magic('more')(less)
366
363
367 # Man calls a pager, so we also need to redefine it
364 # Man calls a pager, so we also need to redefine it
368 if os.name == 'posix':
365 if os.name == 'posix':
369 @line_magic
366 @line_magic
370 def man(self, arg_s):
367 def man(self, arg_s):
371 """Find the man page for the given command and display in pager."""
368 """Find the man page for the given command and display in pager."""
372 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
369 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
373 split=False))
370 split=False))
374
371
375 @line_magic
372 @line_magic
376 def connect_info(self, arg_s):
373 def connect_info(self, arg_s):
377 """Print information for connecting other clients to this kernel
374 """Print information for connecting other clients to this kernel
378
375
379 It will print the contents of this session's connection file, as well as
376 It will print the contents of this session's connection file, as well as
380 shortcuts for local clients.
377 shortcuts for local clients.
381
378
382 In the simplest case, when called from the most recently launched kernel,
379 In the simplest case, when called from the most recently launched kernel,
383 secondary clients can be connected, simply with:
380 secondary clients can be connected, simply with:
384
381
385 $> ipython <app> --existing
382 $> ipython <app> --existing
386
383
387 """
384 """
388
385
389 from IPython.core.application import BaseIPythonApplication as BaseIPApp
386 from IPython.core.application import BaseIPythonApplication as BaseIPApp
390
387
391 if BaseIPApp.initialized():
388 if BaseIPApp.initialized():
392 app = BaseIPApp.instance()
389 app = BaseIPApp.instance()
393 security_dir = app.profile_dir.security_dir
390 security_dir = app.profile_dir.security_dir
394 profile = app.profile
391 profile = app.profile
395 else:
392 else:
396 profile = 'default'
393 profile = 'default'
397 security_dir = ''
394 security_dir = ''
398
395
399 try:
396 try:
400 connection_file = get_connection_file()
397 connection_file = get_connection_file()
401 info = get_connection_info(unpack=False)
398 info = get_connection_info(unpack=False)
402 except Exception as e:
399 except Exception as e:
403 error("Could not get connection info: %r" % e)
400 error("Could not get connection info: %r" % e)
404 return
401 return
405
402
406 # add profile flag for non-default profile
403 # add profile flag for non-default profile
407 profile_flag = "--profile %s" % profile if profile != 'default' else ""
404 profile_flag = "--profile %s" % profile if profile != 'default' else ""
408
405
409 # if it's in the security dir, truncate to basename
406 # if it's in the security dir, truncate to basename
410 if security_dir == os.path.dirname(connection_file):
407 if security_dir == os.path.dirname(connection_file):
411 connection_file = os.path.basename(connection_file)
408 connection_file = os.path.basename(connection_file)
412
409
413
410
414 print (info + '\n')
411 print (info + '\n')
415 print ("Paste the above JSON into a file, and connect with:\n"
412 print ("Paste the above JSON into a file, and connect with:\n"
416 " $> ipython <app> --existing <file>\n"
413 " $> ipython <app> --existing <file>\n"
417 "or, if you are local, you can connect with just:\n"
414 "or, if you are local, you can connect with just:\n"
418 " $> ipython <app> --existing {0} {1}\n"
415 " $> ipython <app> --existing {0} {1}\n"
419 "or even just:\n"
416 "or even just:\n"
420 " $> ipython <app> --existing {1}\n"
417 " $> ipython <app> --existing {1}\n"
421 "if this is the most recent IPython session you have started.".format(
418 "if this is the most recent IPython session you have started.".format(
422 connection_file, profile_flag
419 connection_file, profile_flag
423 )
420 )
424 )
421 )
425
422
426 @line_magic
423 @line_magic
427 def qtconsole(self, arg_s):
424 def qtconsole(self, arg_s):
428 """Open a qtconsole connected to this kernel.
425 """Open a qtconsole connected to this kernel.
429
426
430 Useful for connecting a qtconsole to running notebooks, for better
427 Useful for connecting a qtconsole to running notebooks, for better
431 debugging.
428 debugging.
432 """
429 """
433
430
434 # %qtconsole should imply bind_kernel for engines:
431 # %qtconsole should imply bind_kernel for engines:
435 try:
432 try:
436 from IPython.parallel import bind_kernel
433 from IPython.parallel import bind_kernel
437 except ImportError:
434 except ImportError:
438 # technically possible, because parallel has higher pyzmq min-version
435 # technically possible, because parallel has higher pyzmq min-version
439 pass
436 pass
440 else:
437 else:
441 bind_kernel()
438 bind_kernel()
442
439
443 try:
440 try:
444 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
441 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
445 except Exception as e:
442 except Exception as e:
446 error("Could not start qtconsole: %r" % e)
443 error("Could not start qtconsole: %r" % e)
447 return
444 return
448
445
449 @line_magic
446 @line_magic
450 def autosave(self, arg_s):
447 def autosave(self, arg_s):
451 """Set the autosave interval in the notebook (in seconds).
448 """Set the autosave interval in the notebook (in seconds).
452
449
453 The default value is 120, or two minutes.
450 The default value is 120, or two minutes.
454 ``%autosave 0`` will disable autosave.
451 ``%autosave 0`` will disable autosave.
455
452
456 This magic only has an effect when called from the notebook interface.
453 This magic only has an effect when called from the notebook interface.
457 It has no effect when called in a startup file.
454 It has no effect when called in a startup file.
458 """
455 """
459
456
460 try:
457 try:
461 interval = int(arg_s)
458 interval = int(arg_s)
462 except ValueError:
459 except ValueError:
463 raise UsageError("%%autosave requires an integer, got %r" % arg_s)
460 raise UsageError("%%autosave requires an integer, got %r" % arg_s)
464
461
465 # javascript wants milliseconds
462 # javascript wants milliseconds
466 milliseconds = 1000 * interval
463 milliseconds = 1000 * interval
467 display(Javascript("IPython.notebook.set_autosave_interval(%i)" % milliseconds),
464 display(Javascript("IPython.notebook.set_autosave_interval(%i)" % milliseconds),
468 include=['application/javascript']
465 include=['application/javascript']
469 )
466 )
470 if interval:
467 if interval:
471 print("Autosaving every %i seconds" % interval)
468 print("Autosaving every %i seconds" % interval)
472 else:
469 else:
473 print("Autosave disabled")
470 print("Autosave disabled")
474
471
475
472
476 class ZMQInteractiveShell(InteractiveShell):
473 class ZMQInteractiveShell(InteractiveShell):
477 """A subclass of InteractiveShell for ZMQ."""
474 """A subclass of InteractiveShell for ZMQ."""
478
475
479 displayhook_class = Type(ZMQShellDisplayHook)
476 displayhook_class = Type(ZMQShellDisplayHook)
480 display_pub_class = Type(ZMQDisplayPublisher)
477 display_pub_class = Type(ZMQDisplayPublisher)
481 data_pub_class = Type(ZMQDataPublisher)
478 data_pub_class = Type(ZMQDataPublisher)
482
479
483 # Override the traitlet in the parent class, because there's no point using
480 # Override the traitlet in the parent class, because there's no point using
484 # readline for the kernel. Can be removed when the readline code is moved
481 # readline for the kernel. Can be removed when the readline code is moved
485 # to the terminal frontend.
482 # to the terminal frontend.
486 colors_force = CBool(True)
483 colors_force = CBool(True)
487 readline_use = CBool(False)
484 readline_use = CBool(False)
488 # autoindent has no meaning in a zmqshell, and attempting to enable it
485 # autoindent has no meaning in a zmqshell, and attempting to enable it
489 # will print a warning in the absence of readline.
486 # will print a warning in the absence of readline.
490 autoindent = CBool(False)
487 autoindent = CBool(False)
491
488
492 exiter = Instance(ZMQExitAutocall)
489 exiter = Instance(ZMQExitAutocall)
493 def _exiter_default(self):
490 def _exiter_default(self):
494 return ZMQExitAutocall(self)
491 return ZMQExitAutocall(self)
495
492
496 def _exit_now_changed(self, name, old, new):
493 def _exit_now_changed(self, name, old, new):
497 """stop eventloop when exit_now fires"""
494 """stop eventloop when exit_now fires"""
498 if new:
495 if new:
499 loop = ioloop.IOLoop.instance()
496 loop = ioloop.IOLoop.instance()
500 loop.add_timeout(time.time()+0.1, loop.stop)
497 loop.add_timeout(time.time()+0.1, loop.stop)
501
498
502 keepkernel_on_exit = None
499 keepkernel_on_exit = None
503
500
504 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
501 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
505 # interactive input being read; we provide event loop support in ipkernel
502 # interactive input being read; we provide event loop support in ipkernel
506 @staticmethod
503 @staticmethod
507 def enable_gui(gui):
504 def enable_gui(gui):
508 from .eventloops import enable_gui as real_enable_gui
505 from .eventloops import enable_gui as real_enable_gui
509 try:
506 try:
510 real_enable_gui(gui)
507 real_enable_gui(gui)
511 except ValueError as e:
508 except ValueError as e:
512 raise UsageError("%s" % e)
509 raise UsageError("%s" % e)
513
510
514 def init_environment(self):
511 def init_environment(self):
515 """Configure the user's environment.
512 """Configure the user's environment.
516
513
517 """
514 """
518 env = os.environ
515 env = os.environ
519 # These two ensure 'ls' produces nice coloring on BSD-derived systems
516 # These two ensure 'ls' produces nice coloring on BSD-derived systems
520 env['TERM'] = 'xterm-color'
517 env['TERM'] = 'xterm-color'
521 env['CLICOLOR'] = '1'
518 env['CLICOLOR'] = '1'
522 # Since normal pagers don't work at all (over pexpect we don't have
519 # Since normal pagers don't work at all (over pexpect we don't have
523 # single-key control of the subprocess), try to disable paging in
520 # single-key control of the subprocess), try to disable paging in
524 # subprocesses as much as possible.
521 # subprocesses as much as possible.
525 env['PAGER'] = 'cat'
522 env['PAGER'] = 'cat'
526 env['GIT_PAGER'] = 'cat'
523 env['GIT_PAGER'] = 'cat'
527
524
528 # And install the payload version of page.
525 # And install the payload version of page.
529 install_payload_page()
526 install_payload_page()
530
527
531 def auto_rewrite_input(self, cmd):
528 def auto_rewrite_input(self, cmd):
532 """Called to show the auto-rewritten input for autocall and friends.
529 """Called to show the auto-rewritten input for autocall and friends.
533
530
534 FIXME: this payload is currently not correctly processed by the
531 FIXME: this payload is currently not correctly processed by the
535 frontend.
532 frontend.
536 """
533 """
537 new = self.prompt_manager.render('rewrite') + cmd
534 new = self.prompt_manager.render('rewrite') + cmd
538 payload = dict(
535 payload = dict(
539 source='auto_rewrite_input',
536 source='auto_rewrite_input',
540 transformed_input=new,
537 transformed_input=new,
541 )
538 )
542 self.payload_manager.write_payload(payload)
539 self.payload_manager.write_payload(payload)
543
540
544 def ask_exit(self):
541 def ask_exit(self):
545 """Engage the exit actions."""
542 """Engage the exit actions."""
546 self.exit_now = True
543 self.exit_now = True
547 payload = dict(
544 payload = dict(
548 source='ask_exit',
545 source='ask_exit',
549 exit=True,
546 exit=True,
550 keepkernel=self.keepkernel_on_exit,
547 keepkernel=self.keepkernel_on_exit,
551 )
548 )
552 self.payload_manager.write_payload(payload)
549 self.payload_manager.write_payload(payload)
553
550
554 def _showtraceback(self, etype, evalue, stb):
551 def _showtraceback(self, etype, evalue, stb):
555
552
556 exc_content = {
553 exc_content = {
557 u'traceback' : stb,
554 u'traceback' : stb,
558 u'ename' : unicode(etype.__name__),
555 u'ename' : unicode(etype.__name__),
559 u'evalue' : py3compat.safe_unicode(evalue),
556 u'evalue' : py3compat.safe_unicode(evalue),
560 }
557 }
561
558
562 dh = self.displayhook
559 dh = self.displayhook
563 # Send exception info over pub socket for other clients than the caller
560 # Send exception info over pub socket for other clients than the caller
564 # to pick up
561 # to pick up
565 topic = None
562 topic = None
566 if dh.topic:
563 if dh.topic:
567 topic = dh.topic.replace(b'pyout', b'pyerr')
564 topic = dh.topic.replace(b'pyout', b'pyerr')
568
565
569 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
566 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
570
567
571 # FIXME - Hack: store exception info in shell object. Right now, the
568 # FIXME - Hack: store exception info in shell object. Right now, the
572 # caller is reading this info after the fact, we need to fix this logic
569 # caller is reading this info after the fact, we need to fix this logic
573 # to remove this hack. Even uglier, we need to store the error status
570 # to remove this hack. Even uglier, we need to store the error status
574 # here, because in the main loop, the logic that sets it is being
571 # here, because in the main loop, the logic that sets it is being
575 # skipped because runlines swallows the exceptions.
572 # skipped because runlines swallows the exceptions.
576 exc_content[u'status'] = u'error'
573 exc_content[u'status'] = u'error'
577 self._reply_content = exc_content
574 self._reply_content = exc_content
578 # /FIXME
575 # /FIXME
579
576
580 return exc_content
577 return exc_content
581
578
582 def set_next_input(self, text):
579 def set_next_input(self, text):
583 """Send the specified text to the frontend to be presented at the next
580 """Send the specified text to the frontend to be presented at the next
584 input cell."""
581 input cell."""
585 payload = dict(
582 payload = dict(
586 source='set_next_input',
583 source='set_next_input',
587 text=text
584 text=text
588 )
585 )
589 self.payload_manager.write_payload(payload)
586 self.payload_manager.write_payload(payload)
590
587
591 #-------------------------------------------------------------------------
588 #-------------------------------------------------------------------------
592 # Things related to magics
589 # Things related to magics
593 #-------------------------------------------------------------------------
590 #-------------------------------------------------------------------------
594
591
595 def init_magics(self):
592 def init_magics(self):
596 super(ZMQInteractiveShell, self).init_magics()
593 super(ZMQInteractiveShell, self).init_magics()
597 self.register_magics(KernelMagics)
594 self.register_magics(KernelMagics)
598 self.magics_manager.register_alias('ed', 'edit')
595 self.magics_manager.register_alias('ed', 'edit')
599
596
600
597
601
598
602 InteractiveShellABC.register(ZMQInteractiveShell)
599 InteractiveShellABC.register(ZMQInteractiveShell)
@@ -1,172 +1,172 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 // EngineInteract
9 // EngineInteract
10 //============================================================================
10 //============================================================================
11
11
12 var key = IPython.utils.keycodes;
12 var key = IPython.utils.keycodes;
13
13
14
14
15 var DirectViewWidget = function (selector, kernel, targets) {
15 var DirectViewWidget = function (selector, kernel, targets) {
16 // The kernel doesn't have to be set at creation time, in that case
16 // The kernel doesn't have to be set at creation time, in that case
17 // it will be null and set_kernel has to be called later.
17 // it will be null and set_kernel has to be called later.
18 this.selector = selector;
18 this.selector = selector;
19 this.element = $(selector);
19 this.element = $(selector);
20 this.kernel = kernel || null;
20 this.kernel = kernel || null;
21 this.code_mirror = null;
21 this.code_mirror = null;
22 this.targets = targets;
22 this.targets = targets;
23 this.create_element();
23 this.create_element();
24 };
24 };
25
25
26
26
27 DirectViewWidget.prototype.create_element = function () {
27 DirectViewWidget.prototype.create_element = function () {
28 this.element.addClass('cell border-box-sizing code_cell vbox');
28 this.element.addClass('cell border-box-sizing code_cell vbox');
29 this.element.attr('tabindex','2');
29 this.element.attr('tabindex','2');
30 this.element.css('padding-right',0);
30 this.element.css('padding-right',0);
31
31
32 var control = $('<div/>').addClass('dv_control').height('30px');
32 var control = $('<div/>').addClass('dv_control').height('30px');
33 var control_label = $('<span/>').html('Select engine(s) to run code on interactively: ');
33 var control_label = $('<span/>').html('Select engine(s) to run code on interactively: ');
34 control_label.css('line-height','30px');
34 control_label.css('line-height','30px');
35 var select = $('<select/>').addClass('dv_select ui-widget ui-widget-content');
35 var select = $('<select/>').addClass('dv_select ui-widget ui-widget-content');
36 select.css('font-size','85%%').css('margin-bottom','5px');
36 select.css('font-size','85%%').css('margin-bottom','5px');
37 var n = this.targets.length;
37 var n = this.targets.length;
38 select.append($('<option/>').html('all').attr('value','all'));
38 select.append($('<option/>').html('all').attr('value','all'));
39 for (var i=0; i<n; i++) {
39 for (var i=0; i<n; i++) {
40 select.append($('<option/>').html(this.targets[i]).attr('value',this.targets[i]))
40 select.append($('<option/>').html(this.targets[i]).attr('value',this.targets[i]))
41 }
41 }
42 control.append(control_label).append(select);
42 control.append(control_label).append(select);
43
43
44 var input = $('<div></div>').addClass('input hbox');
44 var input = $('<div></div>').addClass('input hbox');
45 var input_area = $('<div/>').addClass('input_area box-flex1');
45 var input_area = $('<div/>').addClass('input_area box-flex1');
46 this.code_mirror = CodeMirror(input_area.get(0), {
46 this.code_mirror = CodeMirror(input_area.get(0), {
47 indentUnit : 4,
47 indentUnit : 4,
48 mode: 'python',
48 mode: 'python',
49 theme: 'ipython',
49 theme: 'ipython',
50 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
50 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
51 });
51 });
52 input.append(input_area);
52 input.append(input_area);
53 var output = $('<div></div>');
53 var output = $('<div></div>');
54
54
55
55
56 this.element.append(control).append(input).append(output);
56 this.element.append(control).append(input).append(output);
57 this.output_area = new IPython.OutputArea(output, false);
57 this.output_area = new IPython.OutputArea(output, false);
58
58
59 };
59 };
60
60
61
61
62 DirectViewWidget.prototype.handle_codemirror_keyevent = function (editor, event) {
62 DirectViewWidget.prototype.handle_codemirror_keyevent = function (editor, event) {
63 // This method gets called in CodeMirror's onKeyDown/onKeyPress
63 // This method gets called in CodeMirror's onKeyDown/onKeyPress
64 // handlers and is used to provide custom key handling. Its return
64 // handlers and is used to provide custom key handling. Its return
65 // value is used to determine if CodeMirror should ignore the event:
65 // value is used to determine if CodeMirror should ignore the event:
66 // true = ignore, false = don't ignore.
66 // true = ignore, false = don't ignore.
67
67
68 var that = this;
68 var that = this;
69 var cur = editor.getCursor();
69 var cur = editor.getCursor();
70
70
71 if (event.keyCode === key.ENTER && event.shiftKey && event.type === 'keydown') {
71 if (event.keyCode === key.ENTER && event.shiftKey && event.type === 'keydown') {
72 // Always ignore shift-enter in CodeMirror as we handle it.
72 // Always ignore shift-enter in CodeMirror as we handle it.
73 event.stop();
73 event.stop();
74 that.execute();
74 that.execute();
75 return true;
75 return true;
76 } else if (event.keyCode === key.UP && event.type === 'keydown') {
76 } else if (event.keyCode === key.UP && event.type === 'keydown') {
77 event.stop();
77 event.stop();
78 return false;
78 return false;
79 } else if (event.keyCode === key.DOWN && event.type === 'keydown') {
79 } else if (event.keyCode === key.DOWN && event.type === 'keydown') {
80 event.stop();
80 event.stop();
81 return false;
81 return false;
82 } else if (event.keyCode === key.BACKSPACE && event.type == 'keydown') {
82 } else if (event.keyCode === key.BACKSPACE && event.type == 'keydown') {
83 // If backspace and the line ends with 4 spaces, remove them.
83 // If backspace and the line ends with 4 spaces, remove them.
84 var line = editor.getLine(cur.line);
84 var line = editor.getLine(cur.line);
85 var ending = line.slice(-4);
85 var ending = line.slice(-4);
86 if (ending === ' ') {
86 if (ending === ' ') {
87 editor.replaceRange('',
87 editor.replaceRange('',
88 {line: cur.line, ch: cur.ch-4},
88 {line: cur.line, ch: cur.ch-4},
89 {line: cur.line, ch: cur.ch}
89 {line: cur.line, ch: cur.ch}
90 );
90 );
91 event.stop();
91 event.stop();
92 return true;
92 return true;
93 } else {
93 } else {
94 return false;
94 return false;
95 };
95 };
96 };
96 };
97
97
98 return false;
98 return false;
99 };
99 };
100
100
101
101
102 // Kernel related calls.
102 // Kernel related calls.
103
103
104
104
105 DirectViewWidget.prototype.set_kernel = function (kernel) {
105 DirectViewWidget.prototype.set_kernel = function (kernel) {
106 this.kernel = kernel;
106 this.kernel = kernel;
107 }
107 }
108
108
109
109
110 DirectViewWidget.prototype.execute = function () {
110 DirectViewWidget.prototype.execute = function () {
111 this.output_area.clear_output(true, true, true);
111 this.output_area.clear_output();
112 this.element.addClass("running");
112 this.element.addClass("running");
113 var callbacks = {
113 var callbacks = {
114 'execute_reply': $.proxy(this._handle_execute_reply, this),
114 'execute_reply': $.proxy(this._handle_execute_reply, this),
115 'output': $.proxy(this.output_area.handle_output, this.output_area),
115 'output': $.proxy(this.output_area.handle_output, this.output_area),
116 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
116 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
117 };
117 };
118 var target = this.element.find('.dv_select option:selected').attr('value');
118 var target = this.element.find('.dv_select option:selected').attr('value');
119 if (target === 'all') {
119 if (target === 'all') {
120 target = '"all"';
120 target = '"all"';
121 }
121 }
122 var code = '%(widget_var)s.execute("""'+this.get_text()+'""",targets='+target+')';
122 var code = '%(widget_var)s.execute("""'+this.get_text()+'""",targets='+target+')';
123 var msg_id = this.kernel.execute(code, callbacks, {silent: false});
123 var msg_id = this.kernel.execute(code, callbacks, {silent: false});
124 this.clear_input();
124 this.clear_input();
125 this.code_mirror.focus();
125 this.code_mirror.focus();
126 };
126 };
127
127
128
128
129 DirectViewWidget.prototype._handle_execute_reply = function (content) {
129 DirectViewWidget.prototype._handle_execute_reply = function (content) {
130 this.element.removeClass("running");
130 this.element.removeClass("running");
131 // this.dirty = true;
131 // this.dirty = true;
132 }
132 }
133
133
134 // Basic cell manipulation.
134 // Basic cell manipulation.
135
135
136
136
137 DirectViewWidget.prototype.select_all = function () {
137 DirectViewWidget.prototype.select_all = function () {
138 var start = {line: 0, ch: 0};
138 var start = {line: 0, ch: 0};
139 var nlines = this.code_mirror.lineCount();
139 var nlines = this.code_mirror.lineCount();
140 var last_line = this.code_mirror.getLine(nlines-1);
140 var last_line = this.code_mirror.getLine(nlines-1);
141 var end = {line: nlines-1, ch: last_line.length};
141 var end = {line: nlines-1, ch: last_line.length};
142 this.code_mirror.setSelection(start, end);
142 this.code_mirror.setSelection(start, end);
143 };
143 };
144
144
145
145
146 DirectViewWidget.prototype.clear_input = function () {
146 DirectViewWidget.prototype.clear_input = function () {
147 this.code_mirror.setValue('');
147 this.code_mirror.setValue('');
148 };
148 };
149
149
150
150
151 DirectViewWidget.prototype.get_text = function () {
151 DirectViewWidget.prototype.get_text = function () {
152 return this.code_mirror.getValue();
152 return this.code_mirror.getValue();
153 };
153 };
154
154
155
155
156 DirectViewWidget.prototype.set_text = function (code) {
156 DirectViewWidget.prototype.set_text = function (code) {
157 return this.code_mirror.setValue(code);
157 return this.code_mirror.setValue(code);
158 };
158 };
159
159
160 container.show();
160 container.show();
161 var widget = $('<div/>')
161 var widget = $('<div/>')
162 // When templating over a JSON string, we must use single quotes.
162 // When templating over a JSON string, we must use single quotes.
163 var targets = '%(targets)s';
163 var targets = '%(targets)s';
164 targets = $.parseJSON(targets);
164 targets = $.parseJSON(targets);
165 var eiw = new DirectViewWidget(widget, IPython.notebook.kernel, targets);
165 var eiw = new DirectViewWidget(widget, IPython.notebook.kernel, targets);
166 element.append(widget);
166 element.append(widget);
167 element.css('padding',0);
167 element.css('padding',0);
168 setTimeout(function () {
168 setTimeout(function () {
169 eiw.code_mirror.refresh();
169 eiw.code_mirror.refresh();
170 eiw.code_mirror.focus();
170 eiw.code_mirror.focus();
171 }, 1);
171 }, 1);
172
172
General Comments 0
You need to be logged in to leave comments. Login now