##// END OF EJS Templates
Finishing display system work....
Brian E. Granger -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,274 +1,373 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Top-level display functions for displaying object in different formats.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2008-2010 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 from .displaypub import (
21 21 publish_pretty, publish_html,
22 22 publish_latex, publish_svg,
23 23 publish_png, publish_json,
24 publish_javascript
24 publish_javascript, publish_jpeg
25 25 )
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Main functions
29 29 #-----------------------------------------------------------------------------
30 30
31 31 def display(*objs, **kwargs):
32 32 """Display a Python object in all frontends.
33 33
34 34 By default all representations will be computed and sent to the frontends.
35 35 Frontends can decide which representation is used and how.
36 36
37 37 Parameters
38 38 ----------
39 39 objs : tuple of objects
40 40 The Python objects to display.
41 41 include : list or tuple, optional
42 42 A list of format type strings (MIME types) to include in the
43 43 format data dict. If this is set *only* the format types included
44 44 in this list will be computed.
45 45 exclude : list or tuple, optional
46 46 A list of format type string (MIME types) to exclue in the format
47 47 data dict. If this is set all format types will be computed,
48 48 except for those included in this argument.
49 49 """
50 50 include = kwargs.get('include')
51 51 exclude = kwargs.get('exclude')
52 52
53 53 from IPython.core.interactiveshell import InteractiveShell
54 54 inst = InteractiveShell.instance()
55 55 format = inst.display_formatter.format
56 56 publish = inst.display_pub.publish
57 57
58 58 for obj in objs:
59 59 format_dict = format(obj, include=include, exclude=exclude)
60 60 publish('IPython.core.display.display', format_dict)
61 61
62 62
63 63 def display_pretty(*objs, **kwargs):
64 64 """Display the pretty (default) representation of an object.
65 65
66 66 Parameters
67 67 ----------
68 68 objs : tuple of objects
69 69 The Python objects to display, or if raw=True raw text data to
70 70 display.
71 71 raw : bool
72 72 Are the data objects raw data or Python objects that need to be
73 73 formatted before display? [default: False]
74 74 """
75 75 raw = kwargs.pop('raw',False)
76 76 if raw:
77 77 for obj in objs:
78 78 publish_pretty(obj)
79 79 else:
80 80 display(*objs, include=['text/plain'])
81 81
82 82
83 83 def display_html(*objs, **kwargs):
84 84 """Display the HTML representation of an object.
85 85
86 86 Parameters
87 87 ----------
88 88 objs : tuple of objects
89 The Python objects to display, or if raw=True raw html data to
89 The Python objects to display, or if raw=True raw HTML data to
90 90 display.
91 91 raw : bool
92 92 Are the data objects raw data or Python objects that need to be
93 93 formatted before display? [default: False]
94 94 """
95 95 raw = kwargs.pop('raw',False)
96 96 if raw:
97 97 for obj in objs:
98 98 publish_html(obj)
99 99 else:
100 100 display(*objs, include=['text/plain','text/html'])
101 101
102 102
103 103 def display_svg(*objs, **kwargs):
104 104 """Display the SVG representation of an object.
105 105
106 106 Parameters
107 107 ----------
108 108 objs : tuple of objects
109 109 The Python objects to display, or if raw=True raw svg data to
110 110 display.
111 111 raw : bool
112 112 Are the data objects raw data or Python objects that need to be
113 113 formatted before display? [default: False]
114 114 """
115 115 raw = kwargs.pop('raw',False)
116 116 if raw:
117 117 for obj in objs:
118 118 publish_svg(obj)
119 119 else:
120 120 display(*objs, include=['text/plain','image/svg+xml'])
121 121
122 122
123 123 def display_png(*objs, **kwargs):
124 124 """Display the PNG representation of an object.
125 125
126 126 Parameters
127 127 ----------
128 128 objs : tuple of objects
129 129 The Python objects to display, or if raw=True raw png data to
130 130 display.
131 131 raw : bool
132 132 Are the data objects raw data or Python objects that need to be
133 133 formatted before display? [default: False]
134 134 """
135 135 raw = kwargs.pop('raw',False)
136 136 if raw:
137 137 for obj in objs:
138 138 publish_png(obj)
139 139 else:
140 140 display(*objs, include=['text/plain','image/png'])
141 141
142 142
143 def display_jpeg(*objs, **kwargs):
144 """Display the JPEG representation of an object.
145
146 Parameters
147 ----------
148 objs : tuple of objects
149 The Python objects to display, or if raw=True raw JPEG data to
150 display.
151 raw : bool
152 Are the data objects raw data or Python objects that need to be
153 formatted before display? [default: False]
154 """
155 raw = kwargs.pop('raw',False)
156 if raw:
157 for obj in objs:
158 publish_png(obj)
159 else:
160 display(*objs, include=['text/plain','image/jpeg'])
161
162
143 163 def display_latex(*objs, **kwargs):
144 164 """Display the LaTeX representation of an object.
145 165
146 166 Parameters
147 167 ----------
148 168 objs : tuple of objects
149 169 The Python objects to display, or if raw=True raw latex data to
150 170 display.
151 171 raw : bool
152 172 Are the data objects raw data or Python objects that need to be
153 173 formatted before display? [default: False]
154 174 """
155 175 raw = kwargs.pop('raw',False)
156 176 if raw:
157 177 for obj in objs:
158 178 publish_latex(obj)
159 179 else:
160 180 display(*objs, include=['text/plain','text/latex'])
161 181
162 182
163 183 def display_json(*objs, **kwargs):
164 184 """Display the JSON representation of an object.
165 185
166 186 Parameters
167 187 ----------
168 188 objs : tuple of objects
169 189 The Python objects to display, or if raw=True raw json data to
170 190 display.
171 191 raw : bool
172 192 Are the data objects raw data or Python objects that need to be
173 193 formatted before display? [default: False]
174 194 """
175 195 raw = kwargs.pop('raw',False)
176 196 if raw:
177 197 for obj in objs:
178 198 publish_json(obj)
179 199 else:
180 200 display(*objs, include=['text/plain','application/json'])
181 201
182 202
183 203 def display_javascript(*objs, **kwargs):
184 204 """Display the Javascript representation of an object.
185 205
186 206 Parameters
187 207 ----------
188 208 objs : tuple of objects
189 209 The Python objects to display, or if raw=True raw javascript data to
190 210 display.
191 211 raw : bool
192 212 Are the data objects raw data or Python objects that need to be
193 213 formatted before display? [default: False]
194 214 """
195 215 raw = kwargs.pop('raw',False)
196 216 if raw:
197 217 for obj in objs:
198 218 publish_javascript(obj)
199 219 else:
200 220 display(*objs, include=['text/plain','application/javascript'])
201 221
202 222 #-----------------------------------------------------------------------------
203 223 # Smart classes
204 224 #-----------------------------------------------------------------------------
205 225
206 226
207 227 class DisplayObject(object):
208 228 """An object that wraps data to be displayed."""
209 229
210 def __init__(self, data):
211 """Create a display object given raw data of a MIME type or a URL.
230 _read_flags = 'r'
231
232 def __init__(self, data=None, url=None, filename=None):
233 """Create a display object given raw data.
212 234
213 235 When this object is returned by an expression or passed to the
214 236 display function, it will result in the data being displayed
215 237 in the frontend. The MIME type of the data should match the
216 238 subclasses used, so the Png subclass should be used for 'image/png'
217 239 data. If the data is a URL, the data will first be downloaded
218 and then displayed.
240 and then displayed. If
219 241
220 242 Parameters
221 243 ----------
222 244 data : unicode, str or bytes
223 245 The raw data or a URL to download the data from.
246 url : unicode
247 A URL to download the data from.
248 filename : unicode
249 Path to a local file to load the data from.
224 250 """
225 if data.startswith('http'):
226 import urllib2
227 response = urllib2.urlopen(data)
228 self.data = response.read()
251 if data is not None and data.startswith('http'):
252 self.url = data
253 self.filename = None
254 self.data = None
229 255 else:
230 256 self.data = data
231
257 self.url = url
258 self.filename = None if filename is None else unicode(filename)
259 self.reload()
260
261 def reload(self):
262 """Reload the raw data from file or URL."""
263 if self.filename is not None:
264 with open(self.filename, self._read_flags) as f:
265 self.data = f.read()
266 elif self.url is not None:
267 try:
268 import urllib2
269 response = urllib2.urlopen(self.url)
270 self.data = response.read()
271 except:
272 self.data = None
232 273
233 274 class Pretty(DisplayObject):
234 275
235 276 def _repr_pretty_(self):
236 277 return self.data
237 278
238 279
239 class Html(DisplayObject):
280 class HTML(DisplayObject):
240 281
241 282 def _repr_html_(self):
242 283 return self.data
243 284
244 285
245 class Latex(DisplayObject):
286 class Math(DisplayObject):
246 287
247 288 def _repr_latex_(self):
248 289 return self.data
249 290
250 291
251 class Png(DisplayObject):
252
253 def _repr_png_(self):
254 return self.data
255
256
257 class Svg(DisplayObject):
292 class SVG(DisplayObject):
258 293
259 294 def _repr_svg_(self):
260 295 return self.data
261 296
262 297
263 class Json(DisplayObject):
298 class JSON(DisplayObject):
264 299
265 300 def _repr_json_(self):
266 301 return self.data
267 302
268 303
269 class Javscript(DisplayObject):
304 class Javascript(DisplayObject):
270 305
271 306 def _repr_javascript_(self):
272 307 return self.data
273 308
274 309
310 class Image(DisplayObject):
311
312 _read_flags = 'rb'
313
314 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=False):
315 """Create a display an PNG/JPEG image given raw data.
316
317 When this object is returned by an expression or passed to the
318 display function, it will result in the image being displayed
319 in the frontend.
320
321 Parameters
322 ----------
323 data : unicode, str or bytes
324 The raw data or a URL to download the data from.
325 url : unicode
326 A URL to download the data from.
327 filename : unicode
328 Path to a local file to load the data from.
329 format : unicode
330 The format of the image data (png/jpeg/jpg). If a filename or URL is given
331 for format will be inferred from the filename extension.
332 embed : bool
333 Should the image data be embedded in the notebook using a data URI (True)
334 or be loaded using an <img> tag. Set this to True if you want the image
335 to be viewable later with no internet connection. If a filename is given
336 embed is always set to True.
337 """
338 if filename is not None:
339 ext = self._find_ext(filename)
340 elif url is not None:
341 ext = self._find_ext(url)
342 elif data.startswith('http'):
343 ext = self._find_ext(data)
344 else:
345 ext = None
346 if ext is not None:
347 if ext == u'jpg' or ext == u'jpeg':
348 format = u'jpeg'
349 if ext == u'png':
350 format = u'png'
351 self.format = unicode(format).lower()
352 self.embed = True if filename is not None else embed
353 super(Image, self).__init__(data=data, url=url, filename=filename)
354
355 def reload(self):
356 """Reload the raw data from file or URL."""
357 if self.embed:
358 super(Image,self).reload()
359
360 def _repr_html_(self):
361 if not self.embed:
362 return u'<img src="%s" />' % self.url
363
364 def _repr_png_(self):
365 if self.embed and self.format == u'png':
366 return self.data
367
368 def _repr_jpeg_(self):
369 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
370 return self.data
371
372 def _find_ext(self, s):
373 return unicode(s.split('.')[-1].lower())
@@ -1,276 +1,298 b''
1 1 """An interface for publishing rich data to frontends.
2 2
3 3 There are two components of the display system:
4 4
5 5 * Display formatters, which take a Python object and compute the
6 6 representation of the object in various formats (text, HTML, SVg, etc.).
7 7 * The display publisher that is used to send the representation data to the
8 8 various frontends.
9 9
10 10 This module defines the logic display publishing. The display publisher uses
11 11 the ``display_data`` message type that is defined in the IPython messaging
12 12 spec.
13 13
14 14 Authors:
15 15
16 16 * Brian Granger
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Copyright (C) 2008-2010 The IPython Development Team
21 21 #
22 22 # Distributed under the terms of the BSD License. The full license is in
23 23 # the file COPYING, distributed as part of this software.
24 24 #-----------------------------------------------------------------------------
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Imports
28 28 #-----------------------------------------------------------------------------
29 29
30 30 from __future__ import print_function
31 31
32 32 from IPython.config.configurable import Configurable
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Main payload class
36 36 #-----------------------------------------------------------------------------
37 37
38 38 class DisplayPublisher(Configurable):
39 39 """A traited class that publishes display data to frontends.
40 40
41 41 Instances of this class are created by the main IPython object and should
42 42 be accessed there.
43 43 """
44 44
45 45 def _validate_data(self, source, data, metadata=None):
46 46 """Validate the display data.
47 47
48 48 Parameters
49 49 ----------
50 50 source : str
51 51 The fully dotted name of the callable that created the data, like
52 52 :func:`foo.bar.my_formatter`.
53 53 data : dict
54 54 The formata data dictionary.
55 55 metadata : dict
56 56 Any metadata for the data.
57 57 """
58 58
59 59 if not isinstance(source, (str,unicode)):
60 60 raise TypeError('source must be a str, got: %r' % source)
61 61 if not isinstance(data, dict):
62 62 raise TypeError('data must be a dict, got: %r' % data)
63 63 if metadata is not None:
64 64 if not isinstance(metadata, dict):
65 65 raise TypeError('metadata must be a dict, got: %r' % data)
66 66
67 67 def publish(self, source, data, metadata=None):
68 68 """Publish data and metadata to all frontends.
69 69
70 70 See the ``display_data`` message in the messaging documentation for
71 71 more details about this message type.
72 72
73 73 The following MIME types are currently implemented:
74 74
75 75 * text/plain
76 76 * text/html
77 77 * text/latex
78 78 * application/json
79 79 * application/javascript
80 80 * image/png
81 * image/jpeg
81 82 * image/svg+xml
82 83
83 84 Parameters
84 85 ----------
85 86 source : str
86 87 A string that give the function or method that created the data,
87 88 such as 'IPython.core.page'.
88 89 data : dict
89 90 A dictionary having keys that are valid MIME types (like
90 91 'text/plain' or 'image/svg+xml') and values that are the data for
91 92 that MIME type. The data itself must be a JSON'able data
92 93 structure. Minimally all data should have the 'text/plain' data,
93 94 which can be displayed by all frontends. If more than the plain
94 95 text is given, it is up to the frontend to decide which
95 96 representation to use.
96 97 metadata : dict
97 98 A dictionary for metadata related to the data. This can contain
98 99 arbitrary key, value pairs that frontends can use to interpret
99 100 the data.
100 101 """
101 102 from IPython.utils import io
102 103 # The default is to simply write the plain text data using io.stdout.
103 104 if data.has_key('text/plain'):
104 105 print(data['text/plain'], file=io.stdout)
105 106
106 107
107 108 def publish_display_data(source, data, metadata=None):
108 109 """Publish data and metadata to all frontends.
109 110
110 111 See the ``display_data`` message in the messaging documentation for
111 112 more details about this message type.
112 113
113 114 The following MIME types are currently implemented:
114 115
115 116 * text/plain
116 117 * text/html
117 118 * text/latex
118 119 * application/json
119 120 * application/javascript
120 121 * image/png
122 * image/jpeg
121 123 * image/svg+xml
122 124
123 125 Parameters
124 126 ----------
125 127 source : str
126 128 A string that give the function or method that created the data,
127 129 such as 'IPython.core.page'.
128 130 data : dict
129 131 A dictionary having keys that are valid MIME types (like
130 132 'text/plain' or 'image/svg+xml') and values that are the data for
131 133 that MIME type. The data itself must be a JSON'able data
132 134 structure. Minimally all data should have the 'text/plain' data,
133 135 which can be displayed by all frontends. If more than the plain
134 136 text is given, it is up to the frontend to decide which
135 137 representation to use.
136 138 metadata : dict
137 139 A dictionary for metadata related to the data. This can contain
138 140 arbitrary key, value pairs that frontends can use to interpret
139 141 the data.
140 142 """
141 143 from IPython.core.interactiveshell import InteractiveShell
142 144 InteractiveShell.instance().display_pub.publish(
143 145 source,
144 146 data,
145 147 metadata
146 148 )
147 149
148 150
149 151 def publish_pretty(data, metadata=None):
150 152 """Publish raw text data to all frontends.
151 153
152 154 Parameters
153 155 ----------
154 156 data : unicode
155 157 The raw text data to publish.
156 158 metadata : dict
157 159 A dictionary for metadata related to the data. This can contain
158 160 arbitrary key, value pairs that frontends can use to interpret
159 161 the data.
160 162 """
161 163 publish_display_data(
162 164 u'IPython.core.displaypub.publish_pretty',
163 165 {'text/plain':data},
164 166 metadata=metadata
165 167 )
166 168
167 169
168 170 def publish_html(data, metadata=None):
169 """Publish raw html data to all frontends.
171 """Publish raw HTML data to all frontends.
170 172
171 173 Parameters
172 174 ----------
173 175 data : unicode
174 The raw html data to publish.
176 The raw HTML data to publish.
175 177 metadata : dict
176 178 A dictionary for metadata related to the data. This can contain
177 179 arbitrary key, value pairs that frontends can use to interpret
178 180 the data.
179 181 """
180 182 publish_display_data(
181 183 u'IPython.core.displaypub.publish_html',
182 184 {'text/html':data},
183 185 metadata=metadata
184 186 )
185 187
186 188
187 189 def publish_latex(data, metadata=None):
188 """Publish raw latex data to all frontends.
190 """Publish raw LaTeX data to all frontends.
189 191
190 192 Parameters
191 193 ----------
192 194 data : unicode
193 The raw latex data to publish.
195 The raw LaTeX data to publish.
194 196 metadata : dict
195 197 A dictionary for metadata related to the data. This can contain
196 198 arbitrary key, value pairs that frontends can use to interpret
197 199 the data.
198 200 """
199 201 publish_display_data(
200 202 u'IPython.core.displaypub.publish_latex',
201 203 {'text/latex':data},
202 204 metadata=metadata
203 205 )
204 206
205 207 def publish_png(data, metadata=None):
206 """Publish raw binary png data to all frontends.
208 """Publish raw binary PNG data to all frontends.
207 209
208 210 Parameters
209 211 ----------
210 212 data : str/bytes
211 The raw binary png data to publish.
213 The raw binary PNG data to publish.
212 214 metadata : dict
213 215 A dictionary for metadata related to the data. This can contain
214 216 arbitrary key, value pairs that frontends can use to interpret
215 217 the data.
216 218 """
217 219 publish_display_data(
218 220 u'IPython.core.displaypub.publish_png',
219 221 {'image/png':data},
220 222 metadata=metadata
221 223 )
222 224
225
226 def publish_jpeg(data, metadata=None):
227 """Publish raw binary JPEG data to all frontends.
228
229 Parameters
230 ----------
231 data : str/bytes
232 The raw binary JPEG data to publish.
233 metadata : dict
234 A dictionary for metadata related to the data. This can contain
235 arbitrary key, value pairs that frontends can use to interpret
236 the data.
237 """
238 publish_display_data(
239 u'IPython.core.displaypub.publish_jpeg',
240 {'image/jpeg':data},
241 metadata=metadata
242 )
243
244
223 245 def publish_svg(data, metadata=None):
224 """Publish raw svg data to all frontends.
246 """Publish raw SVG data to all frontends.
225 247
226 248 Parameters
227 249 ----------
228 250 data : unicode
229 The raw svg data to publish.
251 The raw SVG data to publish.
230 252 metadata : dict
231 253 A dictionary for metadata related to the data. This can contain
232 254 arbitrary key, value pairs that frontends can use to interpret
233 255 the data.
234 256 """
235 257 publish_display_data(
236 258 u'IPython.core.displaypub.publish_svg',
237 259 {'image/svg+xml':data},
238 260 metadata=metadata
239 261 )
240 262
241 263 def publish_json(data, metadata=None):
242 """Publish raw json data to all frontends.
264 """Publish raw JSON data to all frontends.
243 265
244 266 Parameters
245 267 ----------
246 268 data : unicode
247 The raw json data to publish.
269 The raw JSON data to publish.
248 270 metadata : dict
249 271 A dictionary for metadata related to the data. This can contain
250 272 arbitrary key, value pairs that frontends can use to interpret
251 273 the data.
252 274 """
253 275 publish_display_data(
254 276 u'IPython.core.displaypub.publish_json',
255 277 {'application/json':data},
256 278 metadata=metadata
257 279 )
258 280
259 281 def publish_javascript(data, metadata=None):
260 """Publish raw javascript data to all frontends.
282 """Publish raw Javascript data to all frontends.
261 283
262 284 Parameters
263 285 ----------
264 286 data : unicode
265 The raw javascript data to publish.
287 The raw Javascript data to publish.
266 288 metadata : dict
267 289 A dictionary for metadata related to the data. This can contain
268 290 arbitrary key, value pairs that frontends can use to interpret
269 291 the data.
270 292 """
271 293 publish_display_data(
272 294 u'IPython.core.displaypub.publish_javascript',
273 295 {'application/javascript':data},
274 296 metadata=metadata
275 297 )
276 298
@@ -1,598 +1,619 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Display formatters.
3 3
4 4
5 5 Authors:
6 6
7 7 * Robert Kern
8 8 * Brian Granger
9 9 """
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (c) 2010, IPython Development Team.
12 12 #
13 13 # Distributed under the terms of the Modified BSD License.
14 14 #
15 15 # The full license is in the file COPYING.txt, distributed with this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # Stdlib imports
23 23 import abc
24 24 import sys
25 25 # We must use StringIO, as cStringIO doesn't handle unicode properly.
26 26 from StringIO import StringIO
27 27
28 28 # Our own imports
29 29 from IPython.config.configurable import Configurable
30 30 from IPython.lib import pretty
31 31 from IPython.utils.traitlets import Bool, Dict, Int, Unicode, CUnicode, ObjectName
32 32
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # The main DisplayFormatter class
36 36 #-----------------------------------------------------------------------------
37 37
38 38
39 39 class DisplayFormatter(Configurable):
40 40
41 41 # When set to true only the default plain text formatter will be used.
42 42 plain_text_only = Bool(False, config=True)
43 43
44 44 # A dict of formatter whose keys are format types (MIME types) and whose
45 45 # values are subclasses of BaseFormatter.
46 46 formatters = Dict(config=True)
47 47 def _formatters_default(self):
48 48 """Activate the default formatters."""
49 49 formatter_classes = [
50 50 PlainTextFormatter,
51 51 HTMLFormatter,
52 52 SVGFormatter,
53 53 PNGFormatter,
54 JPEGFormatter,
54 55 LatexFormatter,
55 56 JSONFormatter,
56 57 JavascriptFormatter
57 58 ]
58 59 d = {}
59 60 for cls in formatter_classes:
60 61 f = cls(config=self.config)
61 62 d[f.format_type] = f
62 63 return d
63 64
64 65 def format(self, obj, include=None, exclude=None):
65 66 """Return a format data dict for an object.
66 67
67 68 By default all format types will be computed.
68 69
69 70 The following MIME types are currently implemented:
70 71
71 72 * text/plain
72 73 * text/html
73 74 * text/latex
74 75 * application/json
75 76 * application/javascript
76 77 * image/png
78 * image/jpeg
77 79 * image/svg+xml
78 80
79 81 Parameters
80 82 ----------
81 83 obj : object
82 84 The Python object whose format data will be computed.
83 85 include : list or tuple, optional
84 86 A list of format type strings (MIME types) to include in the
85 87 format data dict. If this is set *only* the format types included
86 88 in this list will be computed.
87 89 exclude : list or tuple, optional
88 90 A list of format type string (MIME types) to exclue in the format
89 91 data dict. If this is set all format types will be computed,
90 92 except for those included in this argument.
91 93
92 94 Returns
93 95 -------
94 96 format_dict : dict
95 97 A dictionary of key/value pairs, one or each format that was
96 98 generated for the object. The keys are the format types, which
97 99 will usually be MIME type strings and the values and JSON'able
98 100 data structure containing the raw data for the representation in
99 101 that format.
100 102 """
101 103 format_dict = {}
102 104
103 105 # If plain text only is active
104 106 if self.plain_text_only:
105 107 formatter = self.formatters['text/plain']
106 108 try:
107 109 data = formatter(obj)
108 110 except:
109 111 # FIXME: log the exception
110 112 raise
111 113 if data is not None:
112 114 format_dict['text/plain'] = data
113 115 return format_dict
114 116
115 117 for format_type, formatter in self.formatters.items():
116 118 if include is not None:
117 119 if format_type not in include:
118 120 continue
119 121 if exclude is not None:
120 122 if format_type in exclude:
121 123 continue
122 124 try:
123 125 data = formatter(obj)
124 126 except:
125 127 # FIXME: log the exception
126 128 raise
127 129 if data is not None:
128 130 format_dict[format_type] = data
129 131 return format_dict
130 132
131 133 @property
132 134 def format_types(self):
133 135 """Return the format types (MIME types) of the active formatters."""
134 136 return self.formatters.keys()
135 137
136 138
137 139 #-----------------------------------------------------------------------------
138 140 # Formatters for specific format types (text, html, svg, etc.)
139 141 #-----------------------------------------------------------------------------
140 142
141 143
142 144 class FormatterABC(object):
143 145 """ Abstract base class for Formatters.
144 146
145 147 A formatter is a callable class that is responsible for computing the
146 148 raw format data for a particular format type (MIME type). For example,
147 149 an HTML formatter would have a format type of `text/html` and would return
148 150 the HTML representation of the object when called.
149 151 """
150 152 __metaclass__ = abc.ABCMeta
151 153
152 154 # The format type of the data returned, usually a MIME type.
153 155 format_type = 'text/plain'
154 156
155 157 # Is the formatter enabled...
156 158 enabled = True
157 159
158 160 @abc.abstractmethod
159 161 def __call__(self, obj):
160 162 """Return a JSON'able representation of the object.
161 163
162 164 If the object cannot be formatted by this formatter, then return None
163 165 """
164 166 try:
165 167 return repr(obj)
166 168 except TypeError:
167 169 return None
168 170
169 171
170 172 class BaseFormatter(Configurable):
171 173 """A base formatter class that is configurable.
172 174
173 175 This formatter should usually be used as the base class of all formatters.
174 176 It is a traited :class:`Configurable` class and includes an extensible
175 177 API for users to determine how their objects are formatted. The following
176 178 logic is used to find a function to format an given object.
177 179
178 180 1. The object is introspected to see if it has a method with the name
179 181 :attr:`print_method`. If is does, that object is passed to that method
180 182 for formatting.
181 183 2. If no print method is found, three internal dictionaries are consulted
182 184 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
183 185 and :attr:`deferred_printers`.
184 186
185 187 Users should use these dictionaries to register functions that will be
186 188 used to compute the format data for their objects (if those objects don't
187 189 have the special print methods). The easiest way of using these
188 190 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
189 191 methods.
190 192
191 193 If no function/callable is found to compute the format data, ``None`` is
192 194 returned and this format type is not used.
193 195 """
194 196
195 197 format_type = Unicode('text/plain')
196 198
197 199 enabled = Bool(True, config=True)
198 200
199 201 print_method = ObjectName('__repr__')
200 202
201 203 # The singleton printers.
202 204 # Maps the IDs of the builtin singleton objects to the format functions.
203 205 singleton_printers = Dict(config=True)
204 206 def _singleton_printers_default(self):
205 207 return {}
206 208
207 209 # The type-specific printers.
208 210 # Map type objects to the format functions.
209 211 type_printers = Dict(config=True)
210 212 def _type_printers_default(self):
211 213 return {}
212 214
213 215 # The deferred-import type-specific printers.
214 216 # Map (modulename, classname) pairs to the format functions.
215 217 deferred_printers = Dict(config=True)
216 218 def _deferred_printers_default(self):
217 219 return {}
218 220
219 221 def __call__(self, obj):
220 222 """Compute the format for an object."""
221 223 if self.enabled:
222 224 obj_id = id(obj)
223 225 try:
224 226 obj_class = getattr(obj, '__class__', None) or type(obj)
225 227 # First try to find registered singleton printers for the type.
226 228 try:
227 229 printer = self.singleton_printers[obj_id]
228 230 except (TypeError, KeyError):
229 231 pass
230 232 else:
231 233 return printer(obj)
232 234 # Next look for type_printers.
233 235 for cls in pretty._get_mro(obj_class):
234 236 if cls in self.type_printers:
235 237 return self.type_printers[cls](obj)
236 238 else:
237 239 printer = self._in_deferred_types(cls)
238 240 if printer is not None:
239 241 return printer(obj)
240 242 # Finally look for special method names.
241 243 if hasattr(obj_class, self.print_method):
242 244 printer = getattr(obj_class, self.print_method)
243 245 return printer(obj)
244 246 return None
245 247 except Exception:
246 248 pass
247 249 else:
248 250 return None
249 251
250 252 def for_type(self, typ, func):
251 253 """Add a format function for a given type.
252 254
253 255 Parameters
254 256 -----------
255 257 typ : class
256 258 The class of the object that will be formatted using `func`.
257 259 func : callable
258 260 The callable that will be called to compute the format data. The
259 261 call signature of this function is simple, it must take the
260 262 object to be formatted and return the raw data for the given
261 263 format. Subclasses may use a different call signature for the
262 264 `func` argument.
263 265 """
264 266 oldfunc = self.type_printers.get(typ, None)
265 267 if func is not None:
266 268 # To support easy restoration of old printers, we need to ignore
267 269 # Nones.
268 270 self.type_printers[typ] = func
269 271 return oldfunc
270 272
271 273 def for_type_by_name(self, type_module, type_name, func):
272 274 """Add a format function for a type specified by the full dotted
273 275 module and name of the type, rather than the type of the object.
274 276
275 277 Parameters
276 278 ----------
277 279 type_module : str
278 280 The full dotted name of the module the type is defined in, like
279 281 ``numpy``.
280 282 type_name : str
281 283 The name of the type (the class name), like ``dtype``
282 284 func : callable
283 285 The callable that will be called to compute the format data. The
284 286 call signature of this function is simple, it must take the
285 287 object to be formatted and return the raw data for the given
286 288 format. Subclasses may use a different call signature for the
287 289 `func` argument.
288 290 """
289 291 key = (type_module, type_name)
290 292 oldfunc = self.deferred_printers.get(key, None)
291 293 if func is not None:
292 294 # To support easy restoration of old printers, we need to ignore
293 295 # Nones.
294 296 self.deferred_printers[key] = func
295 297 return oldfunc
296 298
297 299 def _in_deferred_types(self, cls):
298 300 """
299 301 Check if the given class is specified in the deferred type registry.
300 302
301 303 Returns the printer from the registry if it exists, and None if the
302 304 class is not in the registry. Successful matches will be moved to the
303 305 regular type registry for future use.
304 306 """
305 307 mod = getattr(cls, '__module__', None)
306 308 name = getattr(cls, '__name__', None)
307 309 key = (mod, name)
308 310 printer = None
309 311 if key in self.deferred_printers:
310 312 # Move the printer over to the regular registry.
311 313 printer = self.deferred_printers.pop(key)
312 314 self.type_printers[cls] = printer
313 315 return printer
314 316
315 317
316 318 class PlainTextFormatter(BaseFormatter):
317 319 """The default pretty-printer.
318 320
319 321 This uses :mod:`IPython.external.pretty` to compute the format data of
320 322 the object. If the object cannot be pretty printed, :func:`repr` is used.
321 323 See the documentation of :mod:`IPython.external.pretty` for details on
322 324 how to write pretty printers. Here is a simple example::
323 325
324 326 def dtype_pprinter(obj, p, cycle):
325 327 if cycle:
326 328 return p.text('dtype(...)')
327 329 if hasattr(obj, 'fields'):
328 330 if obj.fields is None:
329 331 p.text(repr(obj))
330 332 else:
331 333 p.begin_group(7, 'dtype([')
332 334 for i, field in enumerate(obj.descr):
333 335 if i > 0:
334 336 p.text(',')
335 337 p.breakable()
336 338 p.pretty(field)
337 339 p.end_group(7, '])')
338 340 """
339 341
340 342 # The format type of data returned.
341 343 format_type = Unicode('text/plain')
342 344
343 345 # This subclass ignores this attribute as it always need to return
344 346 # something.
345 347 enabled = Bool(True, config=False)
346 348
347 349 # Look for a _repr_pretty_ methods to use for pretty printing.
348 350 print_method = ObjectName('_repr_pretty_')
349 351
350 352 # Whether to pretty-print or not.
351 353 pprint = Bool(True, config=True)
352 354
353 355 # Whether to be verbose or not.
354 356 verbose = Bool(False, config=True)
355 357
356 358 # The maximum width.
357 359 max_width = Int(79, config=True)
358 360
359 361 # The newline character.
360 362 newline = Unicode('\n', config=True)
361 363
362 364 # format-string for pprinting floats
363 365 float_format = Unicode('%r')
364 366 # setter for float precision, either int or direct format-string
365 367 float_precision = CUnicode('', config=True)
366 368
367 369 def _float_precision_changed(self, name, old, new):
368 370 """float_precision changed, set float_format accordingly.
369 371
370 372 float_precision can be set by int or str.
371 373 This will set float_format, after interpreting input.
372 374 If numpy has been imported, numpy print precision will also be set.
373 375
374 376 integer `n` sets format to '%.nf', otherwise, format set directly.
375 377
376 378 An empty string returns to defaults (repr for float, 8 for numpy).
377 379
378 380 This parameter can be set via the '%precision' magic.
379 381 """
380 382
381 383 if '%' in new:
382 384 # got explicit format string
383 385 fmt = new
384 386 try:
385 387 fmt%3.14159
386 388 except Exception:
387 389 raise ValueError("Precision must be int or format string, not %r"%new)
388 390 elif new:
389 391 # otherwise, should be an int
390 392 try:
391 393 i = int(new)
392 394 assert i >= 0
393 395 except ValueError:
394 396 raise ValueError("Precision must be int or format string, not %r"%new)
395 397 except AssertionError:
396 398 raise ValueError("int precision must be non-negative, not %r"%i)
397 399
398 400 fmt = '%%.%if'%i
399 401 if 'numpy' in sys.modules:
400 402 # set numpy precision if it has been imported
401 403 import numpy
402 404 numpy.set_printoptions(precision=i)
403 405 else:
404 406 # default back to repr
405 407 fmt = '%r'
406 408 if 'numpy' in sys.modules:
407 409 import numpy
408 410 # numpy default is 8
409 411 numpy.set_printoptions(precision=8)
410 412 self.float_format = fmt
411 413
412 414 # Use the default pretty printers from IPython.external.pretty.
413 415 def _singleton_printers_default(self):
414 416 return pretty._singleton_pprinters.copy()
415 417
416 418 def _type_printers_default(self):
417 419 d = pretty._type_pprinters.copy()
418 420 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
419 421 return d
420 422
421 423 def _deferred_printers_default(self):
422 424 return pretty._deferred_type_pprinters.copy()
423 425
424 426 #### FormatterABC interface ####
425 427
426 428 def __call__(self, obj):
427 429 """Compute the pretty representation of the object."""
428 430 if not self.pprint:
429 431 try:
430 432 return repr(obj)
431 433 except TypeError:
432 434 return ''
433 435 else:
434 436 # This uses use StringIO, as cStringIO doesn't handle unicode.
435 437 stream = StringIO()
436 438 # self.newline.encode() is a quick fix for issue gh-597. We need to
437 439 # ensure that stream does not get a mix of unicode and bytestrings,
438 440 # or it will cause trouble.
439 441 printer = pretty.RepresentationPrinter(stream, self.verbose,
440 442 self.max_width, self.newline.encode(),
441 443 singleton_pprinters=self.singleton_printers,
442 444 type_pprinters=self.type_printers,
443 445 deferred_pprinters=self.deferred_printers)
444 446 printer.pretty(obj)
445 447 printer.flush()
446 448 return stream.getvalue()
447 449
448 450
449 451 class HTMLFormatter(BaseFormatter):
450 452 """An HTML formatter.
451 453
452 454 To define the callables that compute the HTML representation of your
453 455 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
454 456 or :meth:`for_type_by_name` methods to register functions that handle
455 457 this.
456 458
457 459 The return value of this formatter should be a valid HTML snippet that
458 460 could be injected into an existing DOM. It should *not* include the
459 461 ```<html>`` or ```<body>`` tags.
460 462 """
461 463 format_type = Unicode('text/html')
462 464
463 465 print_method = ObjectName('_repr_html_')
464 466
465 467
466 468 class SVGFormatter(BaseFormatter):
467 469 """An SVG formatter.
468 470
469 471 To define the callables that compute the SVG representation of your
470 472 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
471 473 or :meth:`for_type_by_name` methods to register functions that handle
472 474 this.
473 475
474 476 The return value of this formatter should be valid SVG enclosed in
475 477 ```<svg>``` tags, that could be injected into an existing DOM. It should
476 478 *not* include the ```<html>`` or ```<body>`` tags.
477 479 """
478 480 format_type = Unicode('image/svg+xml')
479 481
480 482 print_method = ObjectName('_repr_svg_')
481 483
482 484
483 485 class PNGFormatter(BaseFormatter):
484 486 """A PNG formatter.
485 487
486 488 To define the callables that compute the PNG representation of your
487 489 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
488 490 or :meth:`for_type_by_name` methods to register functions that handle
489 491 this.
490 492
491 493 The return value of this formatter should be raw PNG data, *not*
492 494 base64 encoded.
493 495 """
494 496 format_type = Unicode('image/png')
495 497
496 498 print_method = ObjectName('_repr_png_')
497 499
498 500
501 class JPEGFormatter(BaseFormatter):
502 """A JPEG formatter.
503
504 To define the callables that compute the JPEG representation of your
505 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
506 or :meth:`for_type_by_name` methods to register functions that handle
507 this.
508
509 The return value of this formatter should be raw JPEG data, *not*
510 base64 encoded.
511 """
512 format_type = Unicode('image/jpeg')
513
514 print_method = ObjectName('_repr_jpeg_')
515
516
499 517 class LatexFormatter(BaseFormatter):
500 518 """A LaTeX formatter.
501 519
502 520 To define the callables that compute the LaTeX representation of your
503 521 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
504 522 or :meth:`for_type_by_name` methods to register functions that handle
505 523 this.
506 524
507 525 The return value of this formatter should be a valid LaTeX equation,
508 526 enclosed in either ```$``` or ```$$```.
509 527 """
510 528 format_type = Unicode('text/latex')
511 529
512 530 print_method = ObjectName('_repr_latex_')
513 531
514 532
515 533 class JSONFormatter(BaseFormatter):
516 534 """A JSON string formatter.
517 535
518 536 To define the callables that compute the JSON string representation of
519 537 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
520 538 or :meth:`for_type_by_name` methods to register functions that handle
521 539 this.
522 540
523 541 The return value of this formatter should be a valid JSON string.
524 542 """
525 543 format_type = Unicode('application/json')
526 544
527 545 print_method = ObjectName('_repr_json_')
528 546
529 547
530 548 class JavascriptFormatter(BaseFormatter):
531 549 """A Javascript formatter.
532 550
533 551 To define the callables that compute the Javascript representation of
534 552 your objects, define a :meth:`_repr_javascript_` method or use the
535 553 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
536 554 that handle this.
537 555
538 556 The return value of this formatter should be valid Javascript code and
539 557 should *not* be enclosed in ```<script>``` tags.
540 558 """
541 559 format_type = Unicode('application/javascript')
542 560
543 561 print_method = ObjectName('_repr_javascript_')
544 562
545 563 FormatterABC.register(BaseFormatter)
546 564 FormatterABC.register(PlainTextFormatter)
547 565 FormatterABC.register(HTMLFormatter)
548 566 FormatterABC.register(SVGFormatter)
549 567 FormatterABC.register(PNGFormatter)
568 FormatterABC.register(JPEGFormatter)
550 569 FormatterABC.register(LatexFormatter)
551 570 FormatterABC.register(JSONFormatter)
552 571 FormatterABC.register(JavascriptFormatter)
553 572
554 573
555 574 def format_display_data(obj, include=None, exclude=None):
556 575 """Return a format data dict for an object.
557 576
558 577 By default all format types will be computed.
559 578
560 579 The following MIME types are currently implemented:
561 580
562 581 * text/plain
563 582 * text/html
564 583 * text/latex
565 584 * application/json
566 585 * application/javascript
567 586 * image/png
587 * image/jpeg
568 588 * image/svg+xml
569 589
570 590 Parameters
571 591 ----------
572 592 obj : object
573 593 The Python object whose format data will be computed.
574 594
575 595 Returns
576 596 -------
577 597 format_dict : dict
578 598 A dictionary of key/value pairs, one or each format that was
579 599 generated for the object. The keys are the format types, which
580 600 will usually be MIME type strings and the values and JSON'able
581 601 data structure containing the raw data for the representation in
582 602 that format.
583 603 include : list or tuple, optional
584 604 A list of format type strings (MIME types) to include in the
585 605 format data dict. If this is set *only* the format types included
586 606 in this list will be computed.
587 607 exclude : list or tuple, optional
588 608 A list of format type string (MIME types) to exclue in the format
589 609 data dict. If this is set all format types will be computed,
590 610 except for those included in this argument.
591 611 """
592 612 from IPython.core.interactiveshell import InteractiveShell
593 613
594 614 InteractiveShell.instance().display_formatter.format(
595 615 obj,
596 616 include,
597 617 exclude
598 618 )
619
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,397 +1,410 b''
1 1
2 2 //============================================================================
3 3 // CodeCell
4 4 //============================================================================
5 5
6 6 var IPython = (function (IPython) {
7 7
8 8 var utils = IPython.utils;
9 9
10 10 var CodeCell = function (notebook) {
11 11 this.code_mirror = null;
12 12 this.input_prompt_number = ' ';
13 13 this.is_completing = false;
14 14 this.completion_cursor = null;
15 15 this.outputs = [];
16 16 IPython.Cell.apply(this, arguments);
17 17 };
18 18
19 19
20 20 CodeCell.prototype = new IPython.Cell();
21 21
22 22
23 23 CodeCell.prototype.create_element = function () {
24 24 var cell = $('<div></div>').addClass('cell border-box-sizing code_cell vbox');
25 25 var input = $('<div></div>').addClass('input hbox');
26 26 input.append($('<div/>').addClass('prompt input_prompt'));
27 27 var input_area = $('<div/>').addClass('input_area box-flex1');
28 28 this.code_mirror = CodeMirror(input_area.get(0), {
29 29 indentUnit : 4,
30 30 enterMode : 'flat',
31 31 tabMode: 'shift',
32 32 mode: 'python',
33 33 theme: 'ipython',
34 34 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
35 35 });
36 36 input.append(input_area);
37 37 var output = $('<div></div>').addClass('output vbox');
38 38 cell.append(input).append(output);
39 39 this.element = cell;
40 40 this.collapse()
41 41 };
42 42
43 43
44 44 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
45 45 // This method gets called in CodeMirror's onKeyDown/onKeyPress handlers and
46 46 // is used to provide custom key handling. Its return value is used to determine
47 47 // if CodeMirror should ignore the event: true = ignore, false = don't ignore.
48 48 if (event.keyCode === 13 && event.shiftKey) {
49 49 // Always ignore shift-enter in CodeMirror as we handle it.
50 50 return true;
51 51 } else if (event.keyCode === 9) {
52 52 var cur = editor.getCursor();
53 53 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur).trim();
54 54 if (pre_cursor === "") {
55 55 // Don't autocomplete if the part of the line before the cursor is empty.
56 56 // In this case, let CodeMirror handle indentation.
57 57 return false;
58 58 } else {
59 59 // Autocomplete the current line.
60 60 event.stop();
61 61 var line = editor.getLine(cur.line);
62 62 this.is_completing = true;
63 63 this.completion_cursor = cur;
64 64 IPython.notebook.complete_cell(this, line, cur.ch);
65 65 return true;
66 66 }
67 67 } else if (event.keyCode === 8) {
68 68 // If backspace and the line ends with 4 spaces, remove them.
69 69 var cur = editor.getCursor();
70 70 var line = editor.getLine(cur.line);
71 71 var ending = line.slice(-4);
72 72 if (ending === ' ') {
73 73 editor.replaceRange('',
74 74 {line: cur.line, ch: cur.ch-4},
75 75 {line: cur.line, ch: cur.ch}
76 76 );
77 77 event.stop();
78 78 return true;
79 79 } else {
80 80 return false;
81 81 };
82 82 } else {
83 83 if (this.is_completing && this.completion_cursor !== editor.getCursor()) {
84 84 this.is_completing = false;
85 85 this.completion_cursor = null;
86 86 }
87 87 return false;
88 88 };
89 89 };
90 90
91 91
92 92 CodeCell.prototype.finish_completing = function (matched_text, matches) {
93 93 if (!this.is_completing || matches.length === 0) {return;}
94 94 // console.log("Got matches", matched_text, matches);
95 95
96 96 var that = this;
97 97 var cur = this.completion_cursor;
98 98 var complete = $('<div/>').addClass('completions');
99 99 var select = $('<select/>').attr('multiple','true');
100 100 for (var i=0; i<matches.length; ++i) {
101 101 select.append($('<option/>').text(matches[i]));
102 102 }
103 103 select.children().first().attr('selected','true');
104 104 select.attr('size',Math.min(10,matches.length));
105 105 var pos = this.code_mirror.cursorCoords();
106 106 complete.css('left',pos.x+'px');
107 107 complete.css('top',pos.yBot+'px');
108 108 complete.append(select);
109 109
110 110 $('body').append(complete);
111 111 var done = false;
112 112
113 113 var insert = function (selected_text) {
114 114 that.code_mirror.replaceRange(
115 115 selected_text,
116 116 {line: cur.line, ch: (cur.ch-matched_text.length)},
117 117 {line: cur.line, ch: cur.ch}
118 118 );
119 119 };
120 120
121 121 var close = function () {
122 122 if (done) return;
123 123 done = true;
124 124 complete.remove();
125 125 that.is_completing = false;
126 126 that.completion_cursor = null;
127 127 };
128 128
129 129 var pick = function () {
130 130 insert(select.val()[0]);
131 131 close();
132 132 setTimeout(function(){that.code_mirror.focus();}, 50);
133 133 };
134 134
135 135 select.blur(close);
136 136 select.keydown(function (event) {
137 137 var code = event.which;
138 138 if (code === 13 || code === 32) {
139 139 // Pressing SPACE or ENTER will cause a pick
140 140 event.stopPropagation();
141 141 event.preventDefault();
142 142 pick();
143 143 } else if (code === 38 || code === 40) {
144 144 // We don't want the document keydown handler to handle UP/DOWN,
145 145 // but we want the default action.
146 146 event.stopPropagation();
147 147 } else {
148 148 // All other key presses simple exit completion.
149 149 event.stopPropagation();
150 150 event.preventDefault();
151 151 close();
152 152 that.code_mirror.focus();
153 153 }
154 154 });
155 155 // Double click also causes a pick.
156 156 select.dblclick(pick);
157 157 select.focus();
158 158 };
159 159
160 160
161 161 CodeCell.prototype.select = function () {
162 162 IPython.Cell.prototype.select.apply(this);
163 163 // Todo: this dance is needed because as of CodeMirror 2.12, focus is
164 164 // not causing the cursor to blink if the editor is empty initially.
165 165 // While this seems to fix the issue, this should be fixed
166 166 // in CodeMirror proper.
167 167 var s = this.code_mirror.getValue();
168 168 if (s === '') this.code_mirror.setValue('.');
169 169 this.code_mirror.focus();
170 170 if (s === '') this.code_mirror.setValue('');
171 171 };
172 172
173 173
174 174 CodeCell.prototype.append_output = function (json) {
175 175 this.expand();
176 176 if (json.output_type === 'pyout') {
177 177 this.append_pyout(json);
178 178 } else if (json.output_type === 'pyerr') {
179 179 this.append_pyerr(json);
180 180 } else if (json.output_type === 'display_data') {
181 181 this.append_display_data(json);
182 182 } else if (json.output_type === 'stream') {
183 183 this.append_stream(json);
184 184 };
185 185 this.outputs.push(json);
186 186 };
187 187
188 188
189 189 CodeCell.prototype.append_pyout = function (json) {
190 190 n = json.prompt_number || ' ';
191 191 var toinsert = $("<div/>").addClass("output_area output_pyout hbox");
192 192 toinsert.append($('<div/>').
193 193 addClass('prompt output_prompt').
194 194 html('Out[' + n + ']:')
195 195 );
196 196 this.append_mime_type(json, toinsert);
197 197 toinsert.children().last().addClass("box_flex1");
198 198 this.element.find("div.output").append(toinsert);
199 199 // If we just output latex, typeset it.
200 200 if (json.latex !== undefined) {
201 201 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
202 202 };
203 203 };
204 204
205 205
206 206 CodeCell.prototype.append_pyerr = function (json) {
207 207 var tb = json.traceback;
208 var s = '';
209 var len = tb.length;
210 for (var i=0; i<len; i++) {
211 s = s + tb[i] + '\n';
212 }
213 s = s + '\n';
214 this.append_text(s);
208 if (tb !== undefined) {
209 var s = '';
210 var len = tb.length;
211 for (var i=0; i<len; i++) {
212 s = s + tb[i] + '\n';
213 }
214 s = s + '\n';
215 this.append_text(s);
216 };
215 217 };
216 218
217 219
218 220 CodeCell.prototype.append_stream = function (json) {
219 221 this.append_text(json.text);
220 222 };
221 223
222 224
223 225 CodeCell.prototype.append_display_data = function (json) {
224 226 this.append_mime_type(json);
225 227 };
226 228
227 229
228 230 CodeCell.prototype.append_mime_type = function (json, element) {
229 231 if (json.html !== undefined) {
230 232 this.append_html(json.html, element);
231 233 } else if (json.latex !== undefined) {
232 234 this.append_latex(json.latex, element);
233 235 // If it is undefined, then we just appended to div.output, which
234 236 // makes the latex visible and we can typeset it. The typesetting
235 237 // has to be done after the latex is on the page.
236 238 if (element === undefined) {
237 239 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
238 240 };
239 241 } else if (json.svg !== undefined) {
240 242 this.append_svg(json.svg, element);
241 243 } else if (json.png !== undefined) {
242 244 this.append_png(json.png, element);
245 } else if (json.jpeg !== undefined) {
246 this.append_jpeg(json.jpeg, element);
243 247 } else if (json.text !== undefined) {
244 248 this.append_text(json.text, element);
245 249 };
246 250 return element;
247 251 };
248 252
249 253
250 254 CodeCell.prototype.append_html = function (html, element) {
251 255 element = element || this.element.find("div.output");
252 256 var toinsert = $("<div/>").addClass("output_area output_html");
253 257 toinsert.append(html);
254 258 element.append(toinsert);
255 259 return element;
256 260 }
257 261
258 262
259 263 CodeCell.prototype.append_text = function (data, element) {
260 264 element = element || this.element.find("div.output");
261 265 var toinsert = $("<div/>").addClass("output_area output_stream");
262 266 toinsert.append($("<pre/>").html(utils.fixConsole(data)));
263 267 element.append(toinsert);
264 268 return element;
265 269 };
266 270
267 271
268 272 CodeCell.prototype.append_svg = function (svg, element) {
269 273 element = element || this.element.find("div.output");
270 274 var toinsert = $("<div/>").addClass("output_area output_svg");
271 275 toinsert.append(svg);
272 276 element.append(toinsert);
273 277 return element;
274 278 };
275 279
276 280
277 281 CodeCell.prototype.append_png = function (png, element) {
278 282 element = element || this.element.find("div.output");
279 283 var toinsert = $("<div/>").addClass("output_area output_png");
280 284 toinsert.append($("<img/>").attr('src','data:image/png;base64,'+png));
281 285 element.append(toinsert);
282 286 return element;
283 287 };
284 288
285 289
290 CodeCell.prototype.append_jpeg = function (jpeg, element) {
291 element = element || this.element.find("div.output");
292 var toinsert = $("<div/>").addClass("output_area output_jpeg");
293 toinsert.append($("<img/>").attr('src','data:image/jpeg;base64,'+jpeg));
294 element.append(toinsert);
295 return element;
296 };
297
298
286 299 CodeCell.prototype.append_latex = function (latex, element) {
287 300 // This method cannot do the typesetting because the latex first has to
288 301 // be on the page.
289 302 element = element || this.element.find("div.output");
290 303 var toinsert = $("<div/>").addClass("output_area output_latex");
291 304 toinsert.append(latex);
292 305 element.append(toinsert);
293 306 return element;
294 307 }
295 308
296 309
297 310 CodeCell.prototype.clear_output = function () {
298 311 this.element.find("div.output").html("");
299 312 this.outputs = [];
300 313 };
301 314
302 315
303 316 CodeCell.prototype.clear_input = function () {
304 317 this.code_mirror.setValue('');
305 318 };
306 319
307 320
308 321 CodeCell.prototype.collapse = function () {
309 322 this.element.find('div.output').hide();
310 323 };
311 324
312 325
313 326 CodeCell.prototype.expand = function () {
314 327 this.element.find('div.output').show();
315 328 };
316 329
317 330
318 331 CodeCell.prototype.set_input_prompt = function (number) {
319 332 var n = number || ' ';
320 333 this.input_prompt_number = n
321 334 this.element.find('div.input_prompt').html('In&nbsp;[' + n + ']:');
322 335 };
323 336
324 337
325 338 CodeCell.prototype.get_code = function () {
326 339 return this.code_mirror.getValue();
327 340 };
328 341
329 342
330 343 CodeCell.prototype.set_code = function (code) {
331 344 return this.code_mirror.setValue(code);
332 345 };
333 346
334 347
335 348 CodeCell.prototype.at_top = function () {
336 349 var cursor = this.code_mirror.getCursor();
337 350 if (cursor.line === 0) {
338 351 return true;
339 352 } else {
340 353 return false;
341 354 }
342 355 };
343 356
344 357
345 358 CodeCell.prototype.at_bottom = function () {
346 359 var cursor = this.code_mirror.getCursor();
347 360 if (cursor.line === (this.code_mirror.lineCount()-1)) {
348 361 return true;
349 362 } else {
350 363 return false;
351 364 }
352 365 };
353 366
354 367
355 368 CodeCell.prototype.fromJSON = function (data) {
356 369 // console.log('Import from JSON:', data);
357 370 if (data.cell_type === 'code') {
358 371 if (data.input !== undefined) {
359 372 this.set_code(data.input);
360 373 }
361 374 if (data.prompt_number !== undefined) {
362 375 this.set_input_prompt(data.prompt_number);
363 376 } else {
364 377 this.set_input_prompt();
365 378 };
366 379 var len = data.outputs.length;
367 380 for (var i=0; i<len; i++) {
368 381 this.append_output(data.outputs[i]);
369 382 };
370 383 };
371 384 };
372 385
373 386
374 387 CodeCell.prototype.toJSON = function () {
375 388 var data = {};
376 389 data.input = this.get_code();
377 390 data.cell_type = 'code';
378 391 if (this.input_prompt_number !== ' ') {
379 392 data.prompt_number = this.input_prompt_number
380 393 };
381 394 var outputs = [];
382 395 var len = this.outputs.length;
383 396 for (var i=0; i<len; i++) {
384 397 outputs[i] = this.outputs[i];
385 398 };
386 399 data.outputs = outputs;
387 400 data.language = 'python';
388 401 // console.log('Export to JSON:',data);
389 402 return data;
390 403 };
391 404
392 405
393 406 IPython.CodeCell = CodeCell;
394 407
395 408 return IPython;
396 409 }(IPython));
397 410
@@ -1,729 +1,732 b''
1 1
2 2 //============================================================================
3 3 // Notebook
4 4 //============================================================================
5 5
6 6 var IPython = (function (IPython) {
7 7
8 8 var utils = IPython.utils;
9 9
10 10 var Notebook = function (selector) {
11 11 this.element = $(selector);
12 12 this.element.scroll();
13 13 this.element.data("notebook", this);
14 14 this.next_prompt_number = 1;
15 15 this.kernel = null;
16 16 this.msg_cell_map = {};
17 17 this.style();
18 18 this.create_elements();
19 19 this.bind_events();
20 20 };
21 21
22 22
23 23 Notebook.prototype.style = function () {
24 24 $('div#notebook').addClass('border-box-sizing');
25 25 };
26 26
27 27
28 28 Notebook.prototype.create_elements = function () {
29 29 // We add this end_space div to the end of the notebook div to:
30 30 // i) provide a margin between the last cell and the end of the notebook
31 31 // ii) to prevent the div from scrolling up when the last cell is being
32 32 // edited, but is too low on the page, which browsers will do automatically.
33 33 this.element.append($('<div class="end_space"></div>').height(50));
34 34 $('div#notebook').addClass('border-box-sizing');
35 35 };
36 36
37 37
38 38 Notebook.prototype.bind_events = function () {
39 39 var that = this;
40 40 $(document).keydown(function (event) {
41 41 // console.log(event);
42 42 if (event.which === 38) {
43 43 var cell = that.selected_cell();
44 44 if (cell.at_top()) {
45 45 event.preventDefault();
46 46 that.select_prev();
47 47 };
48 48 } else if (event.which === 40) {
49 49 var cell = that.selected_cell();
50 50 if (cell.at_bottom()) {
51 51 event.preventDefault();
52 52 that.select_next();
53 53 };
54 54 } else if (event.which === 13 && event.shiftKey) {
55 55 that.execute_selected_cell();
56 56 return false;
57 57 } else if (event.which === 13 && event.ctrlKey) {
58 58 that.execute_selected_cell({terminal:true});
59 59 return false;
60 60 };
61 61 });
62 62
63 63 this.element.bind('collapse_pager', function () {
64 64 var app_height = $('div#main_app').height(); // content height
65 65 var splitter_height = $('div#pager_splitter').outerHeight(true);
66 66 var new_height = app_height - splitter_height;
67 67 that.element.animate({height : new_height + 'px'}, 'fast');
68 68 });
69 69
70 70 this.element.bind('expand_pager', function () {
71 71 var app_height = $('div#main_app').height(); // content height
72 72 var splitter_height = $('div#pager_splitter').outerHeight(true);
73 73 var pager_height = $('div#pager').outerHeight(true);
74 74 var new_height = app_height - pager_height - splitter_height;
75 75 that.element.animate({height : new_height + 'px'}, 'fast');
76 76 });
77 77
78 78 this.element.bind('collapse_left_panel', function () {
79 79 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
80 80 var new_margin = splitter_width;
81 81 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
82 82 });
83 83
84 84 this.element.bind('expand_left_panel', function () {
85 85 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
86 86 var left_panel_width = IPython.left_panel.width;
87 87 var new_margin = splitter_width + left_panel_width;
88 88 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
89 89 });
90 90 };
91 91
92 92
93 93 Notebook.prototype.scroll_to_bottom = function () {
94 94 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
95 95 };
96 96
97 97
98 98 Notebook.prototype.scroll_to_top = function () {
99 99 this.element.animate({scrollTop:0}, 0);
100 100 };
101 101
102 102
103 103 // Cell indexing, retrieval, etc.
104 104
105 105
106 106 Notebook.prototype.cell_elements = function () {
107 107 return this.element.children("div.cell");
108 108 }
109 109
110 110
111 111 Notebook.prototype.ncells = function (cell) {
112 112 return this.cell_elements().length;
113 113 }
114 114
115 115
116 116 // TODO: we are often calling cells as cells()[i], which we should optimize
117 117 // to cells(i) or a new method.
118 118 Notebook.prototype.cells = function () {
119 119 return this.cell_elements().toArray().map(function (e) {
120 120 return $(e).data("cell");
121 121 });
122 122 }
123 123
124 124
125 125 Notebook.prototype.find_cell_index = function (cell) {
126 126 var result = null;
127 127 this.cell_elements().filter(function (index) {
128 128 if ($(this).data("cell") === cell) {
129 129 result = index;
130 130 };
131 131 });
132 132 return result;
133 133 };
134 134
135 135
136 136 Notebook.prototype.index_or_selected = function (index) {
137 137 return index || this.selected_index() || 0;
138 138 }
139 139
140 140
141 141 Notebook.prototype.select = function (index) {
142 142 if (index !== undefined && index >= 0 && index < this.ncells()) {
143 143 if (this.selected_index() !== null) {
144 144 this.selected_cell().unselect();
145 145 };
146 146 this.cells()[index].select();
147 147 if (index === (this.ncells()-1)) {
148 148 this.scroll_to_bottom();
149 149 };
150 150 };
151 151 return this;
152 152 };
153 153
154 154
155 155 Notebook.prototype.select_next = function () {
156 156 var index = this.selected_index();
157 157 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
158 158 this.select(index+1);
159 159 };
160 160 return this;
161 161 };
162 162
163 163
164 164 Notebook.prototype.select_prev = function () {
165 165 var index = this.selected_index();
166 166 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
167 167 this.select(index-1);
168 168 };
169 169 return this;
170 170 };
171 171
172 172
173 173 Notebook.prototype.selected_index = function () {
174 174 var result = null;
175 175 this.cell_elements().filter(function (index) {
176 176 if ($(this).data("cell").selected === true) {
177 177 result = index;
178 178 };
179 179 });
180 180 return result;
181 181 };
182 182
183 183
184 184 Notebook.prototype.cell_for_msg = function (msg_id) {
185 185 var cell_id = this.msg_cell_map[msg_id];
186 186 var result = null;
187 187 this.cell_elements().filter(function (index) {
188 188 cell = $(this).data("cell");
189 189 if (cell.cell_id === cell_id) {
190 190 result = cell;
191 191 };
192 192 });
193 193 return result;
194 194 };
195 195
196 196
197 197 Notebook.prototype.selected_cell = function () {
198 198 return this.cell_elements().eq(this.selected_index()).data("cell");
199 199 }
200 200
201 201
202 202 // Cell insertion, deletion and moving.
203 203
204 204
205 205 Notebook.prototype.delete_cell = function (index) {
206 206 var i = index || this.selected_index();
207 207 if (i !== null && i >= 0 && i < this.ncells()) {
208 208 this.cell_elements().eq(i).remove();
209 209 if (i === (this.ncells())) {
210 210 this.select(i-1);
211 211 } else {
212 212 this.select(i);
213 213 };
214 214 };
215 215 return this;
216 216 };
217 217
218 218
219 219 Notebook.prototype.append_cell = function (cell) {
220 220 this.element.find('div.end_space').before(cell.element);
221 221 return this;
222 222 };
223 223
224 224
225 225 Notebook.prototype.insert_cell_after = function (cell, index) {
226 226 var ncells = this.ncells();
227 227 if (ncells === 0) {
228 228 this.append_cell(cell);
229 229 return this;
230 230 };
231 231 if (index >= 0 && index < ncells) {
232 232 this.cell_elements().eq(index).after(cell.element);
233 233 };
234 234 return this
235 235 };
236 236
237 237
238 238 Notebook.prototype.insert_cell_before = function (cell, index) {
239 239 var ncells = this.ncells();
240 240 if (ncells === 0) {
241 241 this.append_cell(cell);
242 242 return this;
243 243 };
244 244 if (index >= 0 && index < ncells) {
245 245 this.cell_elements().eq(index).before(cell.element);
246 246 };
247 247 return this;
248 248 };
249 249
250 250
251 251 Notebook.prototype.move_cell_up = function (index) {
252 252 var i = index || this.selected_index();
253 253 if (i !== null && i < this.ncells() && i > 0) {
254 254 var pivot = this.cell_elements().eq(i-1);
255 255 var tomove = this.cell_elements().eq(i);
256 256 if (pivot !== null && tomove !== null) {
257 257 tomove.detach();
258 258 pivot.before(tomove);
259 259 this.select(i-1);
260 260 };
261 261 };
262 262 return this;
263 263 }
264 264
265 265
266 266 Notebook.prototype.move_cell_down = function (index) {
267 267 var i = index || this.selected_index();
268 268 if (i !== null && i < (this.ncells()-1) && i >= 0) {
269 269 var pivot = this.cell_elements().eq(i+1)
270 270 var tomove = this.cell_elements().eq(i)
271 271 if (pivot !== null && tomove !== null) {
272 272 tomove.detach();
273 273 pivot.after(tomove);
274 274 this.select(i+1);
275 275 };
276 276 };
277 277 return this;
278 278 }
279 279
280 280
281 281 Notebook.prototype.sort_cells = function () {
282 282 var ncells = this.ncells();
283 283 var sindex = this.selected_index();
284 284 var swapped;
285 285 do {
286 286 swapped = false
287 287 for (var i=1; i<ncells; i++) {
288 288 current = this.cell_elements().eq(i).data("cell");
289 289 previous = this.cell_elements().eq(i-1).data("cell");
290 290 if (previous.input_prompt_number > current.input_prompt_number) {
291 291 this.move_cell_up(i);
292 292 swapped = true;
293 293 };
294 294 };
295 295 } while (swapped);
296 296 this.select(sindex);
297 297 return this;
298 298 };
299 299
300 300
301 301 Notebook.prototype.insert_code_cell_before = function (index) {
302 302 // TODO: Bounds check for i
303 303 var i = this.index_or_selected(index);
304 304 var cell = new IPython.CodeCell(this);
305 305 cell.set_input_prompt();
306 306 this.insert_cell_before(cell, i);
307 307 this.select(this.find_cell_index(cell));
308 308 return cell;
309 309 }
310 310
311 311
312 312 Notebook.prototype.insert_code_cell_after = function (index) {
313 313 // TODO: Bounds check for i
314 314 var i = this.index_or_selected(index);
315 315 var cell = new IPython.CodeCell(this);
316 316 cell.set_input_prompt();
317 317 this.insert_cell_after(cell, i);
318 318 this.select(this.find_cell_index(cell));
319 319 return cell;
320 320 }
321 321
322 322
323 323 Notebook.prototype.insert_html_cell_before = function (index) {
324 324 // TODO: Bounds check for i
325 325 var i = this.index_or_selected(index);
326 326 var cell = new IPython.HTMLCell(this);
327 327 cell.config_mathjax();
328 328 this.insert_cell_before(cell, i);
329 329 this.select(this.find_cell_index(cell));
330 330 return cell;
331 331 }
332 332
333 333
334 334 Notebook.prototype.insert_html_cell_after = function (index) {
335 335 // TODO: Bounds check for i
336 336 var i = this.index_or_selected(index);
337 337 var cell = new IPython.HTMLCell(this);
338 338 cell.config_mathjax();
339 339 this.insert_cell_after(cell, i);
340 340 this.select(this.find_cell_index(cell));
341 341 return cell;
342 342 }
343 343
344 344
345 345 Notebook.prototype.insert_markdown_cell_before = function (index) {
346 346 // TODO: Bounds check for i
347 347 var i = this.index_or_selected(index);
348 348 var cell = new IPython.MarkdownCell(this);
349 349 cell.config_mathjax();
350 350 this.insert_cell_before(cell, i);
351 351 this.select(this.find_cell_index(cell));
352 352 return cell;
353 353 }
354 354
355 355
356 356 Notebook.prototype.insert_markdown_cell_after = function (index) {
357 357 // TODO: Bounds check for i
358 358 var i = this.index_or_selected(index);
359 359 var cell = new IPython.MarkdownCell(this);
360 360 cell.config_mathjax();
361 361 this.insert_cell_after(cell, i);
362 362 this.select(this.find_cell_index(cell));
363 363 return cell;
364 364 }
365 365
366 366
367 367 Notebook.prototype.to_code = function (index) {
368 368 // TODO: Bounds check for i
369 369 var i = this.index_or_selected(index);
370 370 var source_element = this.cell_elements().eq(i);
371 371 var source_cell = source_element.data("cell");
372 372 if (source_cell instanceof IPython.HTMLCell ||
373 373 source_cell instanceof IPython.MarkdownCell) {
374 374 this.insert_code_cell_after(i);
375 375 var target_cell = this.cells()[i+1];
376 376 target_cell.set_code(source_cell.get_source());
377 377 source_element.remove();
378 378 };
379 379 };
380 380
381 381
382 382 Notebook.prototype.to_markdown = function (index) {
383 383 // TODO: Bounds check for i
384 384 var i = this.index_or_selected(index);
385 385 var source_element = this.cell_elements().eq(i);
386 386 var source_cell = source_element.data("cell");
387 387 var target_cell = null;
388 388 if (source_cell instanceof IPython.CodeCell) {
389 389 this.insert_markdown_cell_after(i);
390 390 var target_cell = this.cells()[i+1];
391 391 var text = source_cell.get_code();
392 392 } else if (source_cell instanceof IPython.HTMLCell) {
393 393 this.insert_markdown_cell_after(i);
394 394 var target_cell = this.cells()[i+1];
395 395 var text = source_cell.get_source();
396 396 if (text === source_cell.placeholder) {
397 397 text = target_cell.placeholder;
398 398 }
399 399 }
400 400 if (target_cell !== null) {
401 401 if (text === "") {text = target_cell.placeholder;};
402 402 target_cell.set_source(text);
403 403 source_element.remove();
404 404 target_cell.edit();
405 405 }
406 406 };
407 407
408 408
409 409 Notebook.prototype.to_html = function (index) {
410 410 // TODO: Bounds check for i
411 411 var i = this.index_or_selected(index);
412 412 var source_element = this.cell_elements().eq(i);
413 413 var source_cell = source_element.data("cell");
414 414 var target_cell = null;
415 415 if (source_cell instanceof IPython.CodeCell) {
416 416 this.insert_html_cell_after(i);
417 417 var target_cell = this.cells()[i+1];
418 418 var text = source_cell.get_code();
419 419 } else if (source_cell instanceof IPython.MarkdownCell) {
420 420 this.insert_html_cell_after(i);
421 421 var target_cell = this.cells()[i+1];
422 422 var text = source_cell.get_source();
423 423 if (text === source_cell.placeholder) {
424 424 text = target_cell.placeholder;
425 425 }
426 426 }
427 427 if (target_cell !== null) {
428 428 if (text === "") {text = target_cell.placeholder;};
429 429 target_cell.set_source(text);
430 430 source_element.remove();
431 431 target_cell.edit();
432 432 }
433 433 };
434 434
435 435
436 436 // Cell collapsing
437 437
438 438 Notebook.prototype.collapse = function (index) {
439 439 var i = this.index_or_selected(index);
440 440 this.cells()[i].collapse();
441 441 };
442 442
443 443
444 444 Notebook.prototype.expand = function (index) {
445 445 var i = this.index_or_selected(index);
446 446 this.cells()[i].expand();
447 447 };
448 448
449 449
450 450 Notebook.prototype.set_autoindent = function (state) {
451 451 var cells = this.cells();
452 452 len = cells.length;
453 453 for (var i=0; i<len; i++) {
454 454 cells[i].set_autoindent(state)
455 455 };
456 456 };
457 457
458 458 // Kernel related things
459 459
460 460 Notebook.prototype.start_kernel = function () {
461 461 this.kernel = new IPython.Kernel();
462 462 var notebook_id = IPython.save_widget.get_notebook_id();
463 463 this.kernel.start_kernel(notebook_id, $.proxy(this.kernel_started, this));
464 464 };
465 465
466 466
467 467 Notebook.prototype.handle_shell_reply = function (e) {
468 468 reply = $.parseJSON(e.data);
469 469 var header = reply.header;
470 470 var content = reply.content;
471 471 var msg_type = header.msg_type;
472 472 // console.log(reply);
473 473 var cell = this.cell_for_msg(reply.parent_header.msg_id);
474 474 if (msg_type === "execute_reply") {
475 475 cell.set_input_prompt(content.execution_count);
476 476 } else if (msg_type === "complete_reply") {
477 477 cell.finish_completing(content.matched_text, content.matches);
478 478 };
479 479 var payload = content.payload || [];
480 480 this.handle_payload(payload);
481 481 };
482 482
483 483
484 484 Notebook.prototype.handle_payload = function (payload) {
485 485 var l = payload.length;
486 486 if (l > 0) {
487 487 IPython.pager.clear();
488 488 IPython.pager.expand();
489 489 };
490 490 for (var i=0; i<l; i++) {
491 491 IPython.pager.append_text(payload[i].text);
492 492 };
493 493 };
494 494
495 495
496 496 Notebook.prototype.handle_iopub_reply = function (e) {
497 497 reply = $.parseJSON(e.data);
498 498 var content = reply.content;
499 499 // console.log(reply);
500 500 var msg_type = reply.header.msg_type;
501 501 var cell = this.cell_for_msg(reply.parent_header.msg_id);
502 502 var output_types = ['stream','display_data','pyout','pyerr'];
503 503 if (output_types.indexOf(msg_type) >= 0) {
504 504 this.handle_output(cell, msg_type, content);
505 505 } else if (msg_type === "status") {
506 506 if (content.execution_state === "busy") {
507 507 IPython.kernel_status_widget.status_busy();
508 508 } else if (content.execution_state === "idle") {
509 509 IPython.kernel_status_widget.status_idle();
510 510 };
511 511 }
512 512 };
513 513
514 514
515 515 Notebook.prototype.handle_output = function (cell, msg_type, content) {
516 516 var json = {};
517 517 json.output_type = msg_type;
518 518 if (msg_type === "stream") {
519 519 json.text = content.data + '\n';
520 520 } else if (msg_type === "display_data") {
521 521 json = this.convert_mime_types(json, content.data);
522 522 } else if (msg_type === "pyout") {
523 523 json.prompt_number = content.execution_count;
524 524 json = this.convert_mime_types(json, content.data);
525 525 } else if (msg_type === "pyerr") {
526 526 json.ename = content.ename;
527 527 json.evalue = content.evalue;
528 528 json.traceback = content.traceback;
529 529 };
530 530 cell.append_output(json);
531 531 };
532 532
533 533
534 534 Notebook.prototype.convert_mime_types = function (json, data) {
535 535 if (data['text/plain'] !== undefined) {
536 536 json.text = data['text/plain'];
537 537 };
538 538 if (data['text/html'] !== undefined) {
539 539 json.html = data['text/html'];
540 540 };
541 541 if (data['image/svg+xml'] !== undefined) {
542 542 json.svg = data['image/svg+xml'];
543 543 };
544 544 if (data['image/png'] !== undefined) {
545 545 json.png = data['image/png'];
546 546 };
547 if (data['image/jpeg'] !== undefined) {
548 json.jpeg = data['image/jpeg'];
549 };
547 550 if (data['text/latex'] !== undefined) {
548 551 json.latex = data['text/latex'];
549 552 };
550 553 if (data['application/json'] !== undefined) {
551 554 json.json = data['application/json'];
552 555 };
553 556 if (data['application/javascript'] !== undefined) {
554 557 json.javascript = data['application/javascript'];
555 558 }
556 559 return json;
557 560 };
558 561
559 562 Notebook.prototype.kernel_started = function () {
560 563 console.log("Kernel started: ", this.kernel.kernel_id);
561 564 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
562 565 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
563 566 };
564 567
565 568
566 569 Notebook.prototype.execute_selected_cell = function (options) {
567 570 // add_new: should a new cell be added if we are at the end of the nb
568 571 // terminal: execute in terminal mode, which stays in the current cell
569 572 default_options = {terminal: false, add_new: true}
570 573 $.extend(default_options, options)
571 574 var that = this;
572 575 var cell = that.selected_cell();
573 576 var cell_index = that.find_cell_index(cell);
574 577 if (cell instanceof IPython.CodeCell) {
575 578 cell.clear_output();
576 579 var code = cell.get_code();
577 580 var msg_id = that.kernel.execute(cell.get_code());
578 581 that.msg_cell_map[msg_id] = cell.cell_id;
579 582 } else if (cell instanceof IPython.HTMLCell) {
580 583 cell.render();
581 584 }
582 585 if (default_options.terminal) {
583 586 cell.clear_input();
584 587 } else {
585 588 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
586 589 that.insert_code_cell_after();
587 590 // If we are adding a new cell at the end, scroll down to show it.
588 591 that.scroll_to_bottom();
589 592 } else {
590 593 that.select(cell_index+1);
591 594 };
592 595 };
593 596 };
594 597
595 598
596 599 Notebook.prototype.execute_all_cells = function () {
597 600 var ncells = this.ncells();
598 601 for (var i=0; i<ncells; i++) {
599 602 this.select(i);
600 603 this.execute_selected_cell({add_new:false});
601 604 };
602 605 this.scroll_to_bottom();
603 606 };
604 607
605 608
606 609 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
607 610 var msg_id = this.kernel.complete(line, cursor_pos);
608 611 this.msg_cell_map[msg_id] = cell.cell_id;
609 612 };
610 613
611 614 // Persistance and loading
612 615
613 616
614 617 Notebook.prototype.fromJSON = function (data) {
615 618 var ncells = this.ncells();
616 619 for (var i=0; i<ncells; i++) {
617 620 // Always delete cell 0 as they get renumbered as they are deleted.
618 621 this.delete_cell(0);
619 622 };
620 623 // Only handle 1 worksheet for now.
621 624 var worksheet = data.worksheets[0];
622 625 if (worksheet !== undefined) {
623 626 var new_cells = worksheet.cells;
624 627 ncells = new_cells.length;
625 628 var cell_data = null;
626 629 var new_cell = null;
627 630 for (var i=0; i<ncells; i++) {
628 631 cell_data = new_cells[i];
629 632 if (cell_data.cell_type == 'code') {
630 633 new_cell = this.insert_code_cell_after();
631 634 new_cell.fromJSON(cell_data);
632 635 } else if (cell_data.cell_type === 'html') {
633 636 new_cell = this.insert_html_cell_after();
634 637 new_cell.fromJSON(cell_data);
635 638 } else if (cell_data.cell_type === 'markdown') {
636 639 new_cell = this.insert_markdown_cell_after();
637 640 new_cell.fromJSON(cell_data);
638 641 };
639 642 };
640 643 };
641 644 };
642 645
643 646
644 647 Notebook.prototype.toJSON = function () {
645 648 var cells = this.cells();
646 649 var ncells = cells.length;
647 650 cell_array = new Array(ncells);
648 651 for (var i=0; i<ncells; i++) {
649 652 cell_array[i] = cells[i].toJSON();
650 653 };
651 654 data = {
652 655 // Only handle 1 worksheet for now.
653 656 worksheets : [{cells:cell_array}]
654 657 }
655 658 return data
656 659 };
657 660
658 661 Notebook.prototype.save_notebook = function () {
659 662 if (IPython.save_widget.test_notebook_name()) {
660 663 var notebook_id = IPython.save_widget.get_notebook_id();
661 664 var nbname = IPython.save_widget.get_notebook_name();
662 665 // We may want to move the name/id/nbformat logic inside toJSON?
663 666 var data = this.toJSON();
664 667 data.name = nbname;
665 668 data.nbformat = 2;
666 669 data.id = notebook_id
667 670 // We do the call with settings so we can set cache to false.
668 671 var settings = {
669 672 processData : false,
670 673 cache : false,
671 674 type : "PUT",
672 675 data : JSON.stringify(data),
673 676 headers : {'Content-Type': 'application/json'},
674 677 success : $.proxy(this.notebook_saved,this)
675 678 };
676 679 IPython.save_widget.status_saving();
677 680 $.ajax("/notebooks/" + notebook_id, settings);
678 681 };
679 682 };
680 683
681 684
682 685 Notebook.prototype.notebook_saved = function (data, status, xhr) {
683 686 IPython.save_widget.status_save();
684 687 }
685 688
686 689
687 690 Notebook.prototype.load_notebook = function (callback) {
688 691 var that = this;
689 692 var notebook_id = IPython.save_widget.get_notebook_id();
690 693 // We do the call with settings so we can set cache to false.
691 694 var settings = {
692 695 processData : false,
693 696 cache : false,
694 697 type : "GET",
695 698 dataType : "json",
696 699 success : function (data, status, xhr) {
697 700 that.notebook_loaded(data, status, xhr);
698 701 if (callback !== undefined) {
699 702 callback();
700 703 };
701 704 }
702 705 };
703 706 IPython.save_widget.status_loading();
704 707 $.ajax("/notebooks/" + notebook_id, settings);
705 708 }
706 709
707 710
708 711 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
709 712 this.fromJSON(data);
710 713 if (this.ncells() === 0) {
711 714 this.insert_code_cell_after();
712 715 };
713 716 IPython.save_widget.status_save();
714 717 IPython.save_widget.set_notebook_name(data.name);
715 718 this.start_kernel();
716 719 // fromJSON always selects the last cell inserted. We need to wait
717 720 // until that is done before scrolling to the top.
718 721 setTimeout(function () {
719 722 IPython.notebook.select(0);
720 723 IPython.notebook.scroll_to_top();
721 724 }, 50);
722 725 };
723 726
724 727 IPython.Notebook = Notebook;
725 728
726 729 return IPython;
727 730
728 731 }(IPython));
729 732
@@ -1,106 +1,108 b''
1 1 """The basic dict based notebook format."""
2 2
3 3 import pprint
4 4 import uuid
5 5
6 6 from IPython.utils.ipstruct import Struct
7 7
8 8
9 9 class NotebookNode(Struct):
10 10 pass
11 11
12 12
13 13 def from_dict(d):
14 14 if isinstance(d, dict):
15 15 newd = NotebookNode()
16 16 for k,v in d.items():
17 17 newd[k] = from_dict(v)
18 18 return newd
19 19 elif isinstance(d, (tuple, list)):
20 20 return [from_dict(i) for i in d]
21 21 else:
22 22 return d
23 23
24 24
25 25 def new_output(output_type=None, output_text=None, output_png=None,
26 26 output_html=None, output_svg=None, output_latex=None, output_json=None,
27 output_javascript=None, prompt_number=None):
27 output_javascript=None, output_jpeg=None, prompt_number=None):
28 28 """Create a new code cell with input and output"""
29 29 output = NotebookNode()
30 30 if output_type is not None:
31 31 output.output_type = unicode(output_type)
32 32 if output_text is not None:
33 33 output.text = unicode(output_text)
34 34 if output_png is not None:
35 35 output.png = bytes(output_png)
36 if output_jpeg is not None:
37 output.jpeg = bytes(output_jpeg)
36 38 if output_html is not None:
37 39 output.html = unicode(output_html)
38 40 if output_svg is not None:
39 41 output.svg = unicode(output_svg)
40 42 if output_latex is not None:
41 43 output.latex = unicode(output_latex)
42 44 if output_json is not None:
43 45 output.json = unicode(output_json)
44 46 if output_javascript is not None:
45 47 output.javascript = unicode(output_javascript)
46 48 if prompt_number is not None:
47 49 output.prompt_number = int(prompt_number)
48 50 return output
49 51
50 52
51 53 def new_code_cell(input=None, prompt_number=None, outputs=None, language=u'python'):
52 54 """Create a new code cell with input and output"""
53 55 cell = NotebookNode()
54 56 cell.cell_type = u'code'
55 57 if language is not None:
56 58 cell.language = unicode(language)
57 59 if input is not None:
58 60 cell.input = unicode(input)
59 61 if prompt_number is not None:
60 62 cell.prompt_number = int(prompt_number)
61 63 if outputs is None:
62 64 cell.outputs = []
63 65 else:
64 66 cell.outputs = outputs
65 67
66 68 return cell
67 69
68 70 def new_text_cell(cell_type, source=None, rendered=None):
69 71 """Create a new text cell."""
70 72 cell = NotebookNode()
71 73 if source is not None:
72 74 cell.source = unicode(source)
73 75 if rendered is not None:
74 76 cell.rendered = unicode(rendered)
75 77 cell.cell_type = cell_type
76 78 return cell
77 79
78 80
79 81 def new_worksheet(name=None, cells=None):
80 82 """Create a worksheet by name with with a list of cells."""
81 83 ws = NotebookNode()
82 84 if name is not None:
83 85 ws.name = unicode(name)
84 86 if cells is None:
85 87 ws.cells = []
86 88 else:
87 89 ws.cells = list(cells)
88 90 return ws
89 91
90 92
91 93 def new_notebook(name=None, id=None, worksheets=None):
92 94 """Create a notebook by name, id and a list of worksheets."""
93 95 nb = NotebookNode()
94 96 nb.nbformat = 2
95 97 if name is not None:
96 98 nb.name = unicode(name)
97 99 if id is None:
98 100 nb.id = unicode(uuid.uuid4())
99 101 else:
100 102 nb.id = unicode(id)
101 103 if worksheets is None:
102 104 nb.worksheets = []
103 105 else:
104 106 nb.worksheets = list(worksheets)
105 107 return nb
106 108
@@ -1,178 +1,180 b''
1 1 """Read and write notebook files as XML."""
2 2
3 3 from base64 import encodestring, decodestring
4 4 from xml.etree import ElementTree as ET
5 5
6 6 from .rwbase import NotebookReader, NotebookWriter
7 7 from .nbbase import (
8 8 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
9 9 )
10 10
11 11 def indent(elem, level=0):
12 12 i = "\n" + level*" "
13 13 if len(elem):
14 14 if not elem.text or not elem.text.strip():
15 15 elem.text = i + " "
16 16 if not elem.tail or not elem.tail.strip():
17 17 elem.tail = i
18 18 for elem in elem:
19 19 indent(elem, level+1)
20 20 if not elem.tail or not elem.tail.strip():
21 21 elem.tail = i
22 22 else:
23 23 if level and (not elem.tail or not elem.tail.strip()):
24 24 elem.tail = i
25 25
26 26
27 27 def _get_text(e, tag):
28 28 sub_e = e.find(tag)
29 29 if sub_e is None:
30 30 return None
31 31 else:
32 32 return sub_e.text
33 33
34 34
35 35 def _set_text(nbnode, attr, parent, tag):
36 36 if attr in nbnode:
37 37 e = ET.SubElement(parent, tag)
38 38 e.text = nbnode[attr]
39 39
40 40
41 41 def _get_int(e, tag):
42 42 sub_e = e.find(tag)
43 43 if sub_e is None:
44 44 return None
45 45 else:
46 46 return int(sub_e.text)
47 47
48 48
49 49 def _set_int(nbnode, attr, parent, tag):
50 50 if attr in nbnode:
51 51 e = ET.SubElement(parent, tag)
52 52 e.text = unicode(nbnode[attr])
53 53
54 54
55 55 def _get_binary(e, tag):
56 56 sub_e = e.find(tag)
57 57 if sub_e is None:
58 58 return None
59 59 else:
60 60 return decodestring(sub_e.text)
61 61
62 62
63 63 def _set_binary(nbnode, attr, parent, tag):
64 64 if attr in nbnode:
65 65 e = ET.SubElement(parent, tag)
66 66 e.text = encodestring(nbnode[attr])
67 67
68 68
69 69 class XMLReader(NotebookReader):
70 70
71 71 def reads(self, s, **kwargs):
72 72 root = ET.fromstring(s)
73 73 return self.to_notebook(root, **kwargs)
74 74
75 75 def to_notebook(self, root, **kwargs):
76 76 nbname = _get_text(root,'name')
77 77 nbid = _get_text(root,'id')
78 78
79 79 worksheets = []
80 80 for ws_e in root.find('worksheets').getiterator('worksheet'):
81 81 wsname = _get_text(ws_e,'name')
82 82 cells = []
83 83 for cell_e in ws_e.find('cells').getiterator():
84 84 if cell_e.tag == 'codecell':
85 85 input = _get_text(cell_e,'input')
86 86 prompt_number = _get_int(cell_e,'prompt_number')
87 87 language = _get_text(cell_e,'language')
88 88 outputs = []
89 89 for output_e in cell_e.find('outputs').getiterator('output'):
90 90 out_prompt_number = _get_int(output_e,'prompt_number')
91 91 output_type = _get_text(output_e,'output_type')
92 92 output_text = _get_text(output_e,'text')
93 93 output_png = _get_binary(output_e,'png')
94 output_jpeg = _get_binary(output_e,'jpeg')
94 95 output_svg = _get_text(output_e,'svg')
95 96 output_html = _get_text(output_e,'html')
96 97 output_latex = _get_text(output_e,'latex')
97 98 output_json = _get_text(output_e,'json')
98 99 output_javascript = _get_text(output_e,'javascript')
99 100 output = new_output(output_type=output_type,output_png=output_png,
100 output_text=output_text,output_svg=output_svg,
101 output_html=output_html,output_latex=output_latex,
102 output_json=output_json,output_javascript=output_javascript,
103 prompt_number=out_prompt_number
101 output_text=output_text, output_svg=output_svg,
102 output_html=output_html, output_latex=output_latex,
103 output_json=output_json, output_javascript=output_javascript,
104 output_jpeg=output_jpeg, prompt_number=out_prompt_number
104 105 )
105 106 outputs.append(output)
106 107 cc = new_code_cell(input=input,prompt_number=prompt_number,
107 108 language=language,outputs=outputs)
108 109 cells.append(cc)
109 110 if cell_e.tag == 'htmlcell':
110 111 source = _get_text(cell_e,'source')
111 112 rendered = _get_text(cell_e,'rendered')
112 113 cells.append(new_text_cell(u'html', source=source, rendered=rendered))
113 114 if cell_e.tag == 'markdowncell':
114 115 source = _get_text(cell_e,'source')
115 116 rendered = _get_text(cell_e,'rendered')
116 117 cells.append(new_text_cell(u'markdown', source=source, rendered=rendered))
117 118 ws = new_worksheet(name=wsname,cells=cells)
118 119 worksheets.append(ws)
119 120
120 121 nb = new_notebook(name=nbname,id=nbid,worksheets=worksheets)
121 122 return nb
122 123
123 124
124 125 class XMLWriter(NotebookWriter):
125 126
126 127 def writes(self, nb, **kwargs):
127 128 nb_e = ET.Element('notebook')
128 129 _set_text(nb,'name',nb_e,'name')
129 130 _set_text(nb,'id',nb_e,'id')
130 131 _set_int(nb,'nbformat',nb_e,'nbformat')
131 132 wss_e = ET.SubElement(nb_e,'worksheets')
132 133 for ws in nb.worksheets:
133 134 ws_e = ET.SubElement(wss_e, 'worksheet')
134 135 _set_text(ws,'name',ws_e,'name')
135 136 cells_e = ET.SubElement(ws_e,'cells')
136 137 for cell in ws.cells:
137 138 cell_type = cell.cell_type
138 139 if cell_type == 'code':
139 140 cell_e = ET.SubElement(cells_e, 'codecell')
140 141 _set_text(cell,'input',cell_e,'input')
141 142 _set_text(cell,'language',cell_e,'language')
142 143 _set_int(cell,'prompt_number',cell_e,'prompt_number')
143 144 outputs_e = ET.SubElement(cell_e, 'outputs')
144 145 for output in cell.outputs:
145 146 output_e = ET.SubElement(outputs_e, 'output')
146 147 _set_int(output,'prompt_number',output_e,'prompt_number')
147 148 _set_text(output,'output_type',output_e,'output_type')
148 149 _set_text(output,'text',output_e,'text')
149 150 _set_binary(output,'png',output_e,'png')
151 _set_binary(output,'jpeg',output_e,'jpeg')
150 152 _set_text(output,'html',output_e,'html')
151 153 _set_text(output,'svg',output_e,'svg')
152 154 _set_text(output,'latex',output_e,'latex')
153 155 _set_text(output,'json',output_e,'json')
154 156 _set_text(output,'javascript',output_e,'javascript')
155 157 elif cell_type == 'html':
156 158 cell_e = ET.SubElement(cells_e, 'htmlcell')
157 159 _set_text(cell,'source',cell_e,'source')
158 160 _set_text(cell,'rendered',cell_e,'rendered')
159 161 elif cell_type == 'markdown':
160 162 cell_e = ET.SubElement(cells_e, 'markdowncell')
161 163 _set_text(cell,'source',cell_e,'source')
162 164 _set_text(cell,'rendered',cell_e,'rendered')
163 165
164 166 indent(nb_e)
165 167 txt = ET.tostring(nb_e, encoding="utf-8")
166 168 txt = '<?xml version="1.0" encoding="utf-8"?>\n' + txt
167 169 return txt
168 170
169 171
170 172 _reader = XMLReader()
171 173 _writer = XMLWriter()
172 174
173 175 reads = _reader.reads
174 176 read = _reader.read
175 177 to_notebook = _reader.to_notebook
176 178 write = _writer.write
177 179 writes = _writer.writes
178 180
@@ -1,46 +1,50 b''
1 1 from base64 import encodestring, decodestring
2 2 import pprint
3 3
4 4 def base64_decode(nb):
5 5 """Base64 encode all bytes objects in the notebook."""
6 6 for ws in nb.worksheets:
7 7 for cell in ws.cells:
8 8 if cell.cell_type == 'code':
9 9 if 'png' in cell:
10 10 cell.png = bytes(decodestring(cell.png))
11 if 'jpeg' in cell:
12 cell.jpeg = bytes(decodestring(cell.jpeg))
11 13 return nb
12 14
13 15
14 16 def base64_encode(nb):
15 17 """Base64 decode all binary objects in the notebook."""
16 18 for ws in nb.worksheets:
17 19 for cell in ws.cells:
18 20 if cell.cell_type == 'code':
19 21 if 'png' in cell:
20 22 cell.png = unicode(encodestring(cell.png))
23 if 'jpeg' in cell:
24 cell.jpeg = unicode(encodestring(cell.jpeg))
21 25 return nb
22 26
23 27
24 28 class NotebookReader(object):
25 29
26 30 def reads(self, s, **kwargs):
27 31 """Read a notebook from a string."""
28 32 raise NotImplementedError("loads must be implemented in a subclass")
29 33
30 34 def read(self, fp, **kwargs):
31 35 """Read a notebook from a file like object"""
32 36 return self.read(fp.read(), **kwargs)
33 37
34 38
35 39 class NotebookWriter(object):
36 40
37 41 def writes(self, nb, **kwargs):
38 42 """Write a notebook to a string."""
39 43 raise NotImplementedError("loads must be implemented in a subclass")
40 44
41 45 def write(self, nb, fp, **kwargs):
42 46 """Write a notebook to a file like object"""
43 47 return fp.write(self.writes(nb,**kwargs))
44 48
45 49
46 50
@@ -1,83 +1,85 b''
1 1 from ..nbbase import (
2 2 NotebookNode,
3 3 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
4 4 )
5 5
6 6
7 7
8 8 ws = new_worksheet(name='worksheet1')
9 9
10 10 ws.cells.append(new_text_cell(
11 11 u'html',
12 12 source='Some NumPy Examples',
13 13 rendered='Some NumPy Examples'
14 14 ))
15 15
16 16
17 17 ws.cells.append(new_code_cell(
18 18 input='import numpy',
19 19 prompt_number=1
20 20 ))
21 21
22 22 ws.cells.append(new_text_cell(
23 23 u'markdown',
24 24 source='Some NumPy Examples',
25 25 rendered='Some NumPy Examples'
26 26 ))
27 27
28 28 ws.cells.append(new_code_cell(
29 29 input='a = numpy.random.rand(100)',
30 30 prompt_number=2
31 31 ))
32 32
33 33 ws.cells.append(new_code_cell(
34 34 input='print a',
35 35 prompt_number=3,
36 36 outputs=[new_output(
37 37 output_type=u'pyout',
38 38 output_text=u'<array a>',
39 39 output_html=u'The HTML rep',
40 40 output_latex=u'$a$',
41 41 output_png=b'data',
42 output_jpeg=b'data',
42 43 output_svg=u'<svg>',
43 44 output_json=u'json data',
44 45 output_javascript=u'var i=0;',
45 46 prompt_number=3
46 47 ),new_output(
47 48 output_type=u'display_data',
48 49 output_text=u'<array a>',
49 50 output_html=u'The HTML rep',
50 51 output_latex=u'$a$',
51 52 output_png=b'data',
53 output_jpeg=b'data',
52 54 output_svg=u'<svg>',
53 55 output_json=u'json data',
54 56 output_javascript=u'var i=0;',
55 57 prompt_number=4
56 58 )]
57 59 ))
58 60
59 61 nb0 = new_notebook(
60 62 name='nb0',
61 63 worksheets=[ws, new_worksheet(name='worksheet2')]
62 64 )
63 65
64 66 nb0_py = """# <nbformat>2</nbformat>
65 67
66 68 # <codecell>
67 69
68 70 import numpy
69 71
70 72 # </codecell>
71 73 # <codecell>
72 74
73 75 a = numpy.random.rand(100)
74 76
75 77 # </codecell>
76 78 # <codecell>
77 79
78 80 print a
79 81
80 82 # </codecell>
81 83 """
82 84
83 85
@@ -1,64 +1,68 b''
1 1 import __builtin__
2 2 from base64 import encodestring
3 3
4 4 from IPython.core.displayhook import DisplayHook
5 5 from IPython.utils.traitlets import Instance, Dict
6 6 from session import extract_header, Session
7 7
8 8 class ZMQDisplayHook(object):
9 9 """A simple displayhook that publishes the object's repr over a ZeroMQ
10 10 socket."""
11 11 topic=None
12 12
13 13 def __init__(self, session, pub_socket):
14 14 self.session = session
15 15 self.pub_socket = pub_socket
16 16 self.parent_header = {}
17 17
18 18 def __call__(self, obj):
19 19 if obj is None:
20 20 return
21 21
22 22 __builtin__._ = obj
23 23 msg = self.session.send(self.pub_socket, u'pyout', {u'data':repr(obj)},
24 24 parent=self.parent_header, ident=self.topic)
25 25
26 26 def set_parent(self, parent):
27 27 self.parent_header = extract_header(parent)
28 28
29 29
30 def _encode_png(data):
31 pngdata = data.get('image/png')
30 def _encode_binary(format_dict):
31 pngdata = format_dict.get('image/png')
32 32 if pngdata is not None:
33 data['image/png'] = encodestring(pngdata)
33 format_dict['image/png'] = encodestring(pngdata)
34 jpegdata = format_dict.get('image/jpeg')
35 if jpegdata is not None:
36 format_dict['image/jpeg'] = encodestring(jpegdata)
37
34 38
35 39 class ZMQShellDisplayHook(DisplayHook):
36 40 """A displayhook subclass that publishes data using ZeroMQ. This is intended
37 41 to work with an InteractiveShell instance. It sends a dict of different
38 42 representations of the object."""
39 43
40 44 session = Instance(Session)
41 45 pub_socket = Instance('zmq.Socket')
42 46 parent_header = Dict({})
43 47
44 48 def set_parent(self, parent):
45 49 """Set the parent for outbound messages."""
46 50 self.parent_header = extract_header(parent)
47 51
48 52 def start_displayhook(self):
49 53 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
50 54
51 55 def write_output_prompt(self):
52 56 """Write the output prompt."""
53 57 if self.do_full_cache:
54 58 self.msg['content']['execution_count'] = self.prompt_count
55 59
56 60 def write_format_data(self, format_dict):
57 pngdata = format_dict.get('image/png')
58 _encode_png(format_dict)
61 _encode_binary(format_dict)
59 62 self.msg['content']['data'] = format_dict
60 63
61 64 def finish_displayhook(self):
62 65 """Finish up all displayhook activities."""
63 66 self.session.send(self.pub_socket, self.msg)
64 67 self.msg = None
68
@@ -1,457 +1,457 b''
1 1 """A ZMQ-based subclass of InteractiveShell.
2 2
3 3 This code is meant to ease the refactoring of the base InteractiveShell into
4 4 something with a cleaner architecture for 2-process use, without actually
5 5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 6 we subclass and override what we want to fix. Once this is working well, we
7 7 can go back to the base class and refactor the code for a cleaner inheritance
8 8 implementation that doesn't rely on so much monkeypatching.
9 9
10 10 But this lets us maintain a fully working IPython as we develop the new
11 11 machinery. This should thus be thought of as scaffolding.
12 12 """
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Stdlib
19 19 import inspect
20 20 import os
21 21
22 22 # Our own
23 23 from IPython.core.interactiveshell import (
24 24 InteractiveShell, InteractiveShellABC
25 25 )
26 26 from IPython.core import page
27 27 from IPython.core.autocall import ZMQExitAutocall
28 28 from IPython.core.displaypub import DisplayPublisher
29 29 from IPython.core.macro import Macro
30 30 from IPython.core.magic import MacroToEdit
31 31 from IPython.core.payloadpage import install_payload_page
32 32 from IPython.utils import io
33 33 from IPython.utils.path import get_py_filename
34 34 from IPython.utils.traitlets import Instance, Type, Dict, CBool
35 35 from IPython.utils.warn import warn
36 from IPython.zmq.displayhook import ZMQShellDisplayHook, _encode_png
36 from IPython.zmq.displayhook import ZMQShellDisplayHook, _encode_binary
37 37 from IPython.zmq.session import extract_header
38 38 from session import Session
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Globals and side-effects
42 42 #-----------------------------------------------------------------------------
43 43
44 44 # Install the payload version of page.
45 45 install_payload_page()
46 46
47 47 #-----------------------------------------------------------------------------
48 48 # Functions and classes
49 49 #-----------------------------------------------------------------------------
50 50
51 51 class ZMQDisplayPublisher(DisplayPublisher):
52 52 """A display publisher that publishes data using a ZeroMQ PUB socket."""
53 53
54 54 session = Instance(Session)
55 55 pub_socket = Instance('zmq.Socket')
56 56 parent_header = Dict({})
57 57
58 58 def set_parent(self, parent):
59 59 """Set the parent for outbound messages."""
60 60 self.parent_header = extract_header(parent)
61 61
62 62 def publish(self, source, data, metadata=None):
63 63 if metadata is None:
64 64 metadata = {}
65 65 self._validate_data(source, data, metadata)
66 66 content = {}
67 67 content['source'] = source
68 _encode_png(data)
68 _encode_binary(data)
69 69 content['data'] = data
70 70 content['metadata'] = metadata
71 71 self.session.send(
72 72 self.pub_socket, u'display_data', content,
73 73 parent=self.parent_header
74 74 )
75 75
76 76
77 77 class ZMQInteractiveShell(InteractiveShell):
78 78 """A subclass of InteractiveShell for ZMQ."""
79 79
80 80 displayhook_class = Type(ZMQShellDisplayHook)
81 81 display_pub_class = Type(ZMQDisplayPublisher)
82 82
83 83 # Override the traitlet in the parent class, because there's no point using
84 84 # readline for the kernel. Can be removed when the readline code is moved
85 85 # to the terminal frontend.
86 86
87 87 # FIXME. This is disabled for now, even though it may cause problems under
88 88 # Windows, because it breaks %run in the Qt console. See gh-617 for more
89 89 # details. Re-enable once we've fully tested that %run works in the Qt
90 90 # console with syntax highlighting in tracebacks.
91 91 # readline_use = CBool(False)
92 92 # /FIXME
93 93
94 94 exiter = Instance(ZMQExitAutocall)
95 95 def _exiter_default(self):
96 96 return ZMQExitAutocall(self)
97 97
98 98 keepkernel_on_exit = None
99 99
100 100 def init_environment(self):
101 101 """Configure the user's environment.
102 102
103 103 """
104 104 env = os.environ
105 105 # These two ensure 'ls' produces nice coloring on BSD-derived systems
106 106 env['TERM'] = 'xterm-color'
107 107 env['CLICOLOR'] = '1'
108 108 # Since normal pagers don't work at all (over pexpect we don't have
109 109 # single-key control of the subprocess), try to disable paging in
110 110 # subprocesses as much as possible.
111 111 env['PAGER'] = 'cat'
112 112 env['GIT_PAGER'] = 'cat'
113 113
114 114 def auto_rewrite_input(self, cmd):
115 115 """Called to show the auto-rewritten input for autocall and friends.
116 116
117 117 FIXME: this payload is currently not correctly processed by the
118 118 frontend.
119 119 """
120 120 new = self.displayhook.prompt1.auto_rewrite() + cmd
121 121 payload = dict(
122 122 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
123 123 transformed_input=new,
124 124 )
125 125 self.payload_manager.write_payload(payload)
126 126
127 127 def ask_exit(self):
128 128 """Engage the exit actions."""
129 129 payload = dict(
130 130 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
131 131 exit=True,
132 132 keepkernel=self.keepkernel_on_exit,
133 133 )
134 134 self.payload_manager.write_payload(payload)
135 135
136 136 def _showtraceback(self, etype, evalue, stb):
137 137
138 138 exc_content = {
139 139 u'traceback' : stb,
140 140 u'ename' : unicode(etype.__name__),
141 141 u'evalue' : unicode(evalue)
142 142 }
143 143
144 144 dh = self.displayhook
145 145 # Send exception info over pub socket for other clients than the caller
146 146 # to pick up
147 147 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', exc_content, dh.parent_header)
148 148
149 149 # FIXME - Hack: store exception info in shell object. Right now, the
150 150 # caller is reading this info after the fact, we need to fix this logic
151 151 # to remove this hack. Even uglier, we need to store the error status
152 152 # here, because in the main loop, the logic that sets it is being
153 153 # skipped because runlines swallows the exceptions.
154 154 exc_content[u'status'] = u'error'
155 155 self._reply_content = exc_content
156 156 # /FIXME
157 157
158 158 return exc_content
159 159
160 160 #------------------------------------------------------------------------
161 161 # Magic overrides
162 162 #------------------------------------------------------------------------
163 163 # Once the base class stops inheriting from magic, this code needs to be
164 164 # moved into a separate machinery as well. For now, at least isolate here
165 165 # the magics which this class needs to implement differently from the base
166 166 # class, or that are unique to it.
167 167
168 168 def magic_doctest_mode(self,parameter_s=''):
169 169 """Toggle doctest mode on and off.
170 170
171 171 This mode is intended to make IPython behave as much as possible like a
172 172 plain Python shell, from the perspective of how its prompts, exceptions
173 173 and output look. This makes it easy to copy and paste parts of a
174 174 session into doctests. It does so by:
175 175
176 176 - Changing the prompts to the classic ``>>>`` ones.
177 177 - Changing the exception reporting mode to 'Plain'.
178 178 - Disabling pretty-printing of output.
179 179
180 180 Note that IPython also supports the pasting of code snippets that have
181 181 leading '>>>' and '...' prompts in them. This means that you can paste
182 182 doctests from files or docstrings (even if they have leading
183 183 whitespace), and the code will execute correctly. You can then use
184 184 '%history -t' to see the translated history; this will give you the
185 185 input after removal of all the leading prompts and whitespace, which
186 186 can be pasted back into an editor.
187 187
188 188 With these features, you can switch into this mode easily whenever you
189 189 need to do testing and changes to doctests, without having to leave
190 190 your existing IPython session.
191 191 """
192 192
193 193 from IPython.utils.ipstruct import Struct
194 194
195 195 # Shorthands
196 196 shell = self.shell
197 197 disp_formatter = self.shell.display_formatter
198 198 ptformatter = disp_formatter.formatters['text/plain']
199 199 # dstore is a data store kept in the instance metadata bag to track any
200 200 # changes we make, so we can undo them later.
201 201 dstore = shell.meta.setdefault('doctest_mode', Struct())
202 202 save_dstore = dstore.setdefault
203 203
204 204 # save a few values we'll need to recover later
205 205 mode = save_dstore('mode', False)
206 206 save_dstore('rc_pprint', ptformatter.pprint)
207 207 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
208 208 save_dstore('xmode', shell.InteractiveTB.mode)
209 209
210 210 if mode == False:
211 211 # turn on
212 212 ptformatter.pprint = False
213 213 disp_formatter.plain_text_only = True
214 214 shell.magic_xmode('Plain')
215 215 else:
216 216 # turn off
217 217 ptformatter.pprint = dstore.rc_pprint
218 218 disp_formatter.plain_text_only = dstore.rc_plain_text_only
219 219 shell.magic_xmode(dstore.xmode)
220 220
221 221 # Store new mode and inform on console
222 222 dstore.mode = bool(1-int(mode))
223 223 mode_label = ['OFF','ON'][dstore.mode]
224 224 print('Doctest mode is:', mode_label)
225 225
226 226 # Send the payload back so that clients can modify their prompt display
227 227 payload = dict(
228 228 source='IPython.zmq.zmqshell.ZMQInteractiveShell.magic_doctest_mode',
229 229 mode=dstore.mode)
230 230 self.payload_manager.write_payload(payload)
231 231
232 232 def magic_edit(self,parameter_s='',last_call=['','']):
233 233 """Bring up an editor and execute the resulting code.
234 234
235 235 Usage:
236 236 %edit [options] [args]
237 237
238 238 %edit runs IPython's editor hook. The default version of this hook is
239 239 set to call the __IPYTHON__.rc.editor command. This is read from your
240 240 environment variable $EDITOR. If this isn't found, it will default to
241 241 vi under Linux/Unix and to notepad under Windows. See the end of this
242 242 docstring for how to change the editor hook.
243 243
244 244 You can also set the value of this editor via the command line option
245 245 '-editor' or in your ipythonrc file. This is useful if you wish to use
246 246 specifically for IPython an editor different from your typical default
247 247 (and for Windows users who typically don't set environment variables).
248 248
249 249 This command allows you to conveniently edit multi-line code right in
250 250 your IPython session.
251 251
252 252 If called without arguments, %edit opens up an empty editor with a
253 253 temporary file and will execute the contents of this file when you
254 254 close it (don't forget to save it!).
255 255
256 256
257 257 Options:
258 258
259 259 -n <number>: open the editor at a specified line number. By default,
260 260 the IPython editor hook uses the unix syntax 'editor +N filename', but
261 261 you can configure this by providing your own modified hook if your
262 262 favorite editor supports line-number specifications with a different
263 263 syntax.
264 264
265 265 -p: this will call the editor with the same data as the previous time
266 266 it was used, regardless of how long ago (in your current session) it
267 267 was.
268 268
269 269 -r: use 'raw' input. This option only applies to input taken from the
270 270 user's history. By default, the 'processed' history is used, so that
271 271 magics are loaded in their transformed version to valid Python. If
272 272 this option is given, the raw input as typed as the command line is
273 273 used instead. When you exit the editor, it will be executed by
274 274 IPython's own processor.
275 275
276 276 -x: do not execute the edited code immediately upon exit. This is
277 277 mainly useful if you are editing programs which need to be called with
278 278 command line arguments, which you can then do using %run.
279 279
280 280
281 281 Arguments:
282 282
283 283 If arguments are given, the following possibilites exist:
284 284
285 285 - The arguments are numbers or pairs of colon-separated numbers (like
286 286 1 4:8 9). These are interpreted as lines of previous input to be
287 287 loaded into the editor. The syntax is the same of the %macro command.
288 288
289 289 - If the argument doesn't start with a number, it is evaluated as a
290 290 variable and its contents loaded into the editor. You can thus edit
291 291 any string which contains python code (including the result of
292 292 previous edits).
293 293
294 294 - If the argument is the name of an object (other than a string),
295 295 IPython will try to locate the file where it was defined and open the
296 296 editor at the point where it is defined. You can use `%edit function`
297 297 to load an editor exactly at the point where 'function' is defined,
298 298 edit it and have the file be executed automatically.
299 299
300 300 If the object is a macro (see %macro for details), this opens up your
301 301 specified editor with a temporary file containing the macro's data.
302 302 Upon exit, the macro is reloaded with the contents of the file.
303 303
304 304 Note: opening at an exact line is only supported under Unix, and some
305 305 editors (like kedit and gedit up to Gnome 2.8) do not understand the
306 306 '+NUMBER' parameter necessary for this feature. Good editors like
307 307 (X)Emacs, vi, jed, pico and joe all do.
308 308
309 309 - If the argument is not found as a variable, IPython will look for a
310 310 file with that name (adding .py if necessary) and load it into the
311 311 editor. It will execute its contents with execfile() when you exit,
312 312 loading any code in the file into your interactive namespace.
313 313
314 314 After executing your code, %edit will return as output the code you
315 315 typed in the editor (except when it was an existing file). This way
316 316 you can reload the code in further invocations of %edit as a variable,
317 317 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
318 318 the output.
319 319
320 320 Note that %edit is also available through the alias %ed.
321 321
322 322 This is an example of creating a simple function inside the editor and
323 323 then modifying it. First, start up the editor:
324 324
325 325 In [1]: ed
326 326 Editing... done. Executing edited code...
327 327 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
328 328
329 329 We can then call the function foo():
330 330
331 331 In [2]: foo()
332 332 foo() was defined in an editing session
333 333
334 334 Now we edit foo. IPython automatically loads the editor with the
335 335 (temporary) file where foo() was previously defined:
336 336
337 337 In [3]: ed foo
338 338 Editing... done. Executing edited code...
339 339
340 340 And if we call foo() again we get the modified version:
341 341
342 342 In [4]: foo()
343 343 foo() has now been changed!
344 344
345 345 Here is an example of how to edit a code snippet successive
346 346 times. First we call the editor:
347 347
348 348 In [5]: ed
349 349 Editing... done. Executing edited code...
350 350 hello
351 351 Out[5]: "print 'hello'n"
352 352
353 353 Now we call it again with the previous output (stored in _):
354 354
355 355 In [6]: ed _
356 356 Editing... done. Executing edited code...
357 357 hello world
358 358 Out[6]: "print 'hello world'n"
359 359
360 360 Now we call it with the output #8 (stored in _8, also as Out[8]):
361 361
362 362 In [7]: ed _8
363 363 Editing... done. Executing edited code...
364 364 hello again
365 365 Out[7]: "print 'hello again'n"
366 366
367 367
368 368 Changing the default editor hook:
369 369
370 370 If you wish to write your own editor hook, you can put it in a
371 371 configuration file which you load at startup time. The default hook
372 372 is defined in the IPython.core.hooks module, and you can use that as a
373 373 starting example for further modifications. That file also has
374 374 general instructions on how to set a new hook for use once you've
375 375 defined it."""
376 376
377 377 opts,args = self.parse_options(parameter_s,'prn:')
378 378
379 379 try:
380 380 filename, lineno, _ = self._find_edit_target(args, opts, last_call)
381 381 except MacroToEdit as e:
382 382 # TODO: Implement macro editing over 2 processes.
383 383 print("Macro editing not yet implemented in 2-process model.")
384 384 return
385 385
386 386 # Make sure we send to the client an absolute path, in case the working
387 387 # directory of client and kernel don't match
388 388 filename = os.path.abspath(filename)
389 389
390 390 payload = {
391 391 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
392 392 'filename' : filename,
393 393 'line_number' : lineno
394 394 }
395 395 self.payload_manager.write_payload(payload)
396 396
397 397 def magic_gui(self, *args, **kwargs):
398 398 raise NotImplementedError(
399 399 'Kernel GUI support is not implemented yet, except for --pylab.')
400 400
401 401 def magic_pylab(self, *args, **kwargs):
402 402 raise NotImplementedError(
403 403 'pylab support must be enabled in command line options.')
404 404
405 405 # A few magics that are adapted to the specifics of using pexpect and a
406 406 # remote terminal
407 407
408 408 def magic_clear(self, arg_s):
409 409 """Clear the terminal."""
410 410 if os.name == 'posix':
411 411 self.shell.system("clear")
412 412 else:
413 413 self.shell.system("cls")
414 414
415 415 if os.name == 'nt':
416 416 # This is the usual name in windows
417 417 magic_cls = magic_clear
418 418
419 419 # Terminal pagers won't work over pexpect, but we do have our own pager
420 420
421 421 def magic_less(self, arg_s):
422 422 """Show a file through the pager.
423 423
424 424 Files ending in .py are syntax-highlighted."""
425 425 cont = open(arg_s).read()
426 426 if arg_s.endswith('.py'):
427 427 cont = self.shell.pycolorize(cont)
428 428 page.page(cont)
429 429
430 430 magic_more = magic_less
431 431
432 432 # Man calls a pager, so we also need to redefine it
433 433 if os.name == 'posix':
434 434 def magic_man(self, arg_s):
435 435 """Find the man page for the given command and display in pager."""
436 436 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
437 437 split=False))
438 438
439 439 # FIXME: this is specific to the GUI, so we should let the gui app load
440 440 # magics at startup that are only for the gui. Once the gui app has proper
441 441 # profile and configuration management, we can have it initialize a kernel
442 442 # with a special config file that provides these.
443 443 def magic_guiref(self, arg_s):
444 444 """Show a basic reference about the GUI console."""
445 445 from IPython.core.usage import gui_reference
446 446 page.page(gui_reference, auto_html=True)
447 447
448 448 def set_next_input(self, text):
449 449 """Send the specified text to the frontend to be presented at the next
450 450 input cell."""
451 451 payload = dict(
452 452 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
453 453 text=text
454 454 )
455 455 self.payload_manager.write_payload(payload)
456 456
457 457 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now