##// END OF EJS Templates
Don't send \r characters in zmqshell clear_ourput
jon -
Show More
@@ -1,911 +1,908 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008 The IPython Development Team
2 // Copyright (C) 2008 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // OutputArea
9 // OutputArea
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule OutputArea
15 * @submodule OutputArea
16 */
16 */
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18 "use strict";
19
19
20 var utils = IPython.utils;
20 var utils = IPython.utils;
21
21
22 /**
22 /**
23 * @class OutputArea
23 * @class OutputArea
24 *
24 *
25 * @constructor
25 * @constructor
26 */
26 */
27
27
28 var OutputArea = function (selector, prompt_area) {
28 var OutputArea = function (selector, prompt_area) {
29 this.selector = selector;
29 this.selector = selector;
30 this.wrapper = $(selector);
30 this.wrapper = $(selector);
31 this.outputs = [];
31 this.outputs = [];
32 this.collapsed = false;
32 this.collapsed = false;
33 this.scrolled = false;
33 this.scrolled = false;
34 this.trusted = true;
34 this.trusted = true;
35 this.clear_queued = null;
35 this.clear_queued = null;
36 if (prompt_area === undefined) {
36 if (prompt_area === undefined) {
37 this.prompt_area = true;
37 this.prompt_area = true;
38 } else {
38 } else {
39 this.prompt_area = prompt_area;
39 this.prompt_area = prompt_area;
40 }
40 }
41 this.create_elements();
41 this.create_elements();
42 this.style();
42 this.style();
43 this.bind_events();
43 this.bind_events();
44 };
44 };
45
45
46
46
47 /**
47 /**
48 * Class prototypes
48 * Class prototypes
49 **/
49 **/
50
50
51 OutputArea.prototype.create_elements = function () {
51 OutputArea.prototype.create_elements = function () {
52 this.element = $("<div/>");
52 this.element = $("<div/>");
53 this.collapse_button = $("<div/>");
53 this.collapse_button = $("<div/>");
54 this.prompt_overlay = $("<div/>");
54 this.prompt_overlay = $("<div/>");
55 this.wrapper.append(this.prompt_overlay);
55 this.wrapper.append(this.prompt_overlay);
56 this.wrapper.append(this.element);
56 this.wrapper.append(this.element);
57 this.wrapper.append(this.collapse_button);
57 this.wrapper.append(this.collapse_button);
58 };
58 };
59
59
60
60
61 OutputArea.prototype.style = function () {
61 OutputArea.prototype.style = function () {
62 this.collapse_button.hide();
62 this.collapse_button.hide();
63 this.prompt_overlay.hide();
63 this.prompt_overlay.hide();
64
64
65 this.wrapper.addClass('output_wrapper');
65 this.wrapper.addClass('output_wrapper');
66 this.element.addClass('output');
66 this.element.addClass('output');
67
67
68 this.collapse_button.addClass("btn output_collapsed");
68 this.collapse_button.addClass("btn output_collapsed");
69 this.collapse_button.attr('title', 'click to expand output');
69 this.collapse_button.attr('title', 'click to expand output');
70 this.collapse_button.text('. . .');
70 this.collapse_button.text('. . .');
71
71
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
72 this.prompt_overlay.addClass('out_prompt_overlay prompt');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
73 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
74
74
75 this.collapse();
75 this.collapse();
76 };
76 };
77
77
78 /**
78 /**
79 * Should the OutputArea scroll?
79 * Should the OutputArea scroll?
80 * Returns whether the height (in lines) exceeds a threshold.
80 * Returns whether the height (in lines) exceeds a threshold.
81 *
81 *
82 * @private
82 * @private
83 * @method _should_scroll
83 * @method _should_scroll
84 * @param [lines=100]{Integer}
84 * @param [lines=100]{Integer}
85 * @return {Bool}
85 * @return {Bool}
86 *
86 *
87 */
87 */
88 OutputArea.prototype._should_scroll = function (lines) {
88 OutputArea.prototype._should_scroll = function (lines) {
89 if (lines <=0 ){ return }
89 if (lines <=0 ){ return }
90 if (!lines) {
90 if (!lines) {
91 lines = 100;
91 lines = 100;
92 }
92 }
93 // line-height from http://stackoverflow.com/questions/1185151
93 // line-height from http://stackoverflow.com/questions/1185151
94 var fontSize = this.element.css('font-size');
94 var fontSize = this.element.css('font-size');
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
95 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
96
96
97 return (this.element.height() > lines * lineHeight);
97 return (this.element.height() > lines * lineHeight);
98 };
98 };
99
99
100
100
101 OutputArea.prototype.bind_events = function () {
101 OutputArea.prototype.bind_events = function () {
102 var that = this;
102 var that = this;
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
103 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
104 this.prompt_overlay.click(function () { that.toggle_scroll(); });
105
105
106 this.element.resize(function () {
106 this.element.resize(function () {
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
107 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
108 if ( IPython.utils.browser[0] === "Firefox" ) {
108 if ( IPython.utils.browser[0] === "Firefox" ) {
109 return;
109 return;
110 }
110 }
111 // maybe scroll output,
111 // maybe scroll output,
112 // if it's grown large enough and hasn't already been scrolled.
112 // if it's grown large enough and hasn't already been scrolled.
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
113 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
114 that.scroll_area();
114 that.scroll_area();
115 }
115 }
116 });
116 });
117 this.collapse_button.click(function () {
117 this.collapse_button.click(function () {
118 that.expand();
118 that.expand();
119 });
119 });
120 };
120 };
121
121
122
122
123 OutputArea.prototype.collapse = function () {
123 OutputArea.prototype.collapse = function () {
124 if (!this.collapsed) {
124 if (!this.collapsed) {
125 this.element.hide();
125 this.element.hide();
126 this.prompt_overlay.hide();
126 this.prompt_overlay.hide();
127 if (this.element.html()){
127 if (this.element.html()){
128 this.collapse_button.show();
128 this.collapse_button.show();
129 }
129 }
130 this.collapsed = true;
130 this.collapsed = true;
131 }
131 }
132 };
132 };
133
133
134
134
135 OutputArea.prototype.expand = function () {
135 OutputArea.prototype.expand = function () {
136 if (this.collapsed) {
136 if (this.collapsed) {
137 this.collapse_button.hide();
137 this.collapse_button.hide();
138 this.element.show();
138 this.element.show();
139 this.prompt_overlay.show();
139 this.prompt_overlay.show();
140 this.collapsed = false;
140 this.collapsed = false;
141 }
141 }
142 };
142 };
143
143
144
144
145 OutputArea.prototype.toggle_output = function () {
145 OutputArea.prototype.toggle_output = function () {
146 if (this.collapsed) {
146 if (this.collapsed) {
147 this.expand();
147 this.expand();
148 } else {
148 } else {
149 this.collapse();
149 this.collapse();
150 }
150 }
151 };
151 };
152
152
153
153
154 OutputArea.prototype.scroll_area = function () {
154 OutputArea.prototype.scroll_area = function () {
155 this.element.addClass('output_scroll');
155 this.element.addClass('output_scroll');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
156 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
157 this.scrolled = true;
157 this.scrolled = true;
158 };
158 };
159
159
160
160
161 OutputArea.prototype.unscroll_area = function () {
161 OutputArea.prototype.unscroll_area = function () {
162 this.element.removeClass('output_scroll');
162 this.element.removeClass('output_scroll');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
163 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
164 this.scrolled = false;
164 this.scrolled = false;
165 };
165 };
166
166
167 /**
167 /**
168 *
168 *
169 * Scroll OutputArea if height supperior than a threshold (in lines).
169 * Scroll OutputArea if height supperior than a threshold (in lines).
170 *
170 *
171 * Threshold is a maximum number of lines. If unspecified, defaults to
171 * Threshold is a maximum number of lines. If unspecified, defaults to
172 * OutputArea.minimum_scroll_threshold.
172 * OutputArea.minimum_scroll_threshold.
173 *
173 *
174 * Negative threshold will prevent the OutputArea from ever scrolling.
174 * Negative threshold will prevent the OutputArea from ever scrolling.
175 *
175 *
176 * @method scroll_if_long
176 * @method scroll_if_long
177 *
177 *
178 * @param [lines=20]{Number} Default to 20 if not set,
178 * @param [lines=20]{Number} Default to 20 if not set,
179 * behavior undefined for value of `0`.
179 * behavior undefined for value of `0`.
180 *
180 *
181 **/
181 **/
182 OutputArea.prototype.scroll_if_long = function (lines) {
182 OutputArea.prototype.scroll_if_long = function (lines) {
183 var n = lines | OutputArea.minimum_scroll_threshold;
183 var n = lines | OutputArea.minimum_scroll_threshold;
184 if(n <= 0){
184 if(n <= 0){
185 return
185 return
186 }
186 }
187
187
188 if (this._should_scroll(n)) {
188 if (this._should_scroll(n)) {
189 // only allow scrolling long-enough output
189 // only allow scrolling long-enough output
190 this.scroll_area();
190 this.scroll_area();
191 }
191 }
192 };
192 };
193
193
194
194
195 OutputArea.prototype.toggle_scroll = function () {
195 OutputArea.prototype.toggle_scroll = function () {
196 if (this.scrolled) {
196 if (this.scrolled) {
197 this.unscroll_area();
197 this.unscroll_area();
198 } else {
198 } else {
199 // only allow scrolling long-enough output
199 // only allow scrolling long-enough output
200 this.scroll_if_long();
200 this.scroll_if_long();
201 }
201 }
202 };
202 };
203
203
204
204
205 // typeset with MathJax if MathJax is available
205 // typeset with MathJax if MathJax is available
206 OutputArea.prototype.typeset = function () {
206 OutputArea.prototype.typeset = function () {
207 if (window.MathJax){
207 if (window.MathJax){
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
208 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
209 }
209 }
210 };
210 };
211
211
212
212
213 OutputArea.prototype.handle_output = function (msg) {
213 OutputArea.prototype.handle_output = function (msg) {
214 var json = {};
214 var json = {};
215 var msg_type = json.output_type = msg.header.msg_type;
215 var msg_type = json.output_type = msg.header.msg_type;
216 var content = msg.content;
216 var content = msg.content;
217 if (msg_type === "stream") {
217 if (msg_type === "stream") {
218 json.text = content.data;
218 json.text = content.data;
219 json.stream = content.name;
219 json.stream = content.name;
220 } else if (msg_type === "display_data") {
220 } else if (msg_type === "display_data") {
221 json = content.data;
221 json = content.data;
222 json.output_type = msg_type;
222 json.output_type = msg_type;
223 json.metadata = content.metadata;
223 json.metadata = content.metadata;
224 } else if (msg_type === "pyout") {
224 } else if (msg_type === "pyout") {
225 json = content.data;
225 json = content.data;
226 json.output_type = msg_type;
226 json.output_type = msg_type;
227 json.metadata = content.metadata;
227 json.metadata = content.metadata;
228 json.prompt_number = content.execution_count;
228 json.prompt_number = content.execution_count;
229 } else if (msg_type === "pyerr") {
229 } else if (msg_type === "pyerr") {
230 json.ename = content.ename;
230 json.ename = content.ename;
231 json.evalue = content.evalue;
231 json.evalue = content.evalue;
232 json.traceback = content.traceback;
232 json.traceback = content.traceback;
233 }
233 }
234 this.append_output(json);
234 this.append_output(json);
235 };
235 };
236
236
237
237
238 OutputArea.prototype.rename_keys = function (data, key_map) {
238 OutputArea.prototype.rename_keys = function (data, key_map) {
239 var remapped = {};
239 var remapped = {};
240 for (var key in data) {
240 for (var key in data) {
241 var new_key = key_map[key] || key;
241 var new_key = key_map[key] || key;
242 remapped[new_key] = data[key];
242 remapped[new_key] = data[key];
243 }
243 }
244 return remapped;
244 return remapped;
245 };
245 };
246
246
247
247
248 OutputArea.output_types = [
248 OutputArea.output_types = [
249 'application/javascript',
249 'application/javascript',
250 'text/html',
250 'text/html',
251 'text/latex',
251 'text/latex',
252 'image/svg+xml',
252 'image/svg+xml',
253 'image/png',
253 'image/png',
254 'image/jpeg',
254 'image/jpeg',
255 'application/pdf',
255 'application/pdf',
256 'text/plain'
256 'text/plain'
257 ];
257 ];
258
258
259 OutputArea.prototype.validate_output = function (json) {
259 OutputArea.prototype.validate_output = function (json) {
260 // scrub invalid outputs
260 // scrub invalid outputs
261 // TODO: right now everything is a string, but JSON really shouldn't be.
261 // TODO: right now everything is a string, but JSON really shouldn't be.
262 // nbformat 4 will fix that.
262 // nbformat 4 will fix that.
263 $.map(OutputArea.output_types, function(key){
263 $.map(OutputArea.output_types, function(key){
264 if (json[key] !== undefined && typeof json[key] !== 'string') {
264 if (json[key] !== undefined && typeof json[key] !== 'string') {
265 console.log("Invalid type for " + key, json[key]);
265 console.log("Invalid type for " + key, json[key]);
266 delete json[key];
266 delete json[key];
267 }
267 }
268 });
268 });
269 return json;
269 return json;
270 };
270 };
271
271
272 OutputArea.prototype.append_output = function (json) {
272 OutputArea.prototype.append_output = function (json) {
273 this.expand();
273 this.expand();
274
274
275 // validate output data types
275 // validate output data types
276 json = this.validate_output(json);
276 json = this.validate_output(json);
277 // TODO: Why are we recieving these new line characters for no
278 // reason? They ruin everything.
279 if (json.output_type === 'stream' && !json.text.trim()) return;
280
277
281 // Clear the output if clear is queued.
278 // Clear the output if clear is queued.
282 var needs_height_reset = false;
279 var needs_height_reset = false;
283 if (this.clear_queued) {
280 if (this.clear_queued) {
284 this.clear_output(false);
281 this.clear_output(false);
285 needs_height_reset = true;
282 needs_height_reset = true;
286 }
283 }
287
284
288 if (json.output_type === 'pyout') {
285 if (json.output_type === 'pyout') {
289 this.append_pyout(json);
286 this.append_pyout(json);
290 } else if (json.output_type === 'pyerr') {
287 } else if (json.output_type === 'pyerr') {
291 this.append_pyerr(json);
288 this.append_pyerr(json);
292 } else if (json.output_type === 'stream') {
289 } else if (json.output_type === 'stream') {
293 this.append_stream(json);
290 this.append_stream(json);
294 }
291 }
295
292
296 // We must release the animation fixed height in a callback since Gecko
293 // We must release the animation fixed height in a callback since Gecko
297 // (FireFox) doesn't render the image immediately as the data is
294 // (FireFox) doesn't render the image immediately as the data is
298 // available.
295 // available.
299 var that = this;
296 var that = this;
300 var handle_appended = function ($el) {
297 var handle_appended = function ($el) {
301 // Only reset the height to automatic if the height is currently
298 // Only reset the height to automatic if the height is currently
302 // fixed (done by wait=True flag on clear_output).
299 // fixed (done by wait=True flag on clear_output).
303 if (needs_height_reset) {
300 if (needs_height_reset) {
304 that.element.height('');
301 that.element.height('');
305 }
302 }
306 that.element.trigger('resize');
303 that.element.trigger('resize');
307 };
304 };
308 if (json.output_type === 'display_data') {
305 if (json.output_type === 'display_data') {
309 this.append_display_data(json, handle_appended);
306 this.append_display_data(json, handle_appended);
310 } else {
307 } else {
311 handle_appended();
308 handle_appended();
312 }
309 }
313
310
314 this.outputs.push(json);
311 this.outputs.push(json);
315 };
312 };
316
313
317
314
318 OutputArea.prototype.create_output_area = function () {
315 OutputArea.prototype.create_output_area = function () {
319 var oa = $("<div/>").addClass("output_area");
316 var oa = $("<div/>").addClass("output_area");
320 if (this.prompt_area) {
317 if (this.prompt_area) {
321 oa.append($('<div/>').addClass('prompt'));
318 oa.append($('<div/>').addClass('prompt'));
322 }
319 }
323 return oa;
320 return oa;
324 };
321 };
325
322
326
323
327 function _get_metadata_key(metadata, key, mime) {
324 function _get_metadata_key(metadata, key, mime) {
328 var mime_md = metadata[mime];
325 var mime_md = metadata[mime];
329 // mime-specific higher priority
326 // mime-specific higher priority
330 if (mime_md && mime_md[key] !== undefined) {
327 if (mime_md && mime_md[key] !== undefined) {
331 return mime_md[key];
328 return mime_md[key];
332 }
329 }
333 // fallback on global
330 // fallback on global
334 return metadata[key];
331 return metadata[key];
335 }
332 }
336
333
337 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
334 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
338 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
335 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
339 if (_get_metadata_key(md, 'isolated', mime)) {
336 if (_get_metadata_key(md, 'isolated', mime)) {
340 // Create an iframe to isolate the subarea from the rest of the
337 // Create an iframe to isolate the subarea from the rest of the
341 // document
338 // document
342 var iframe = $('<iframe/>').addClass('box-flex1');
339 var iframe = $('<iframe/>').addClass('box-flex1');
343 iframe.css({'height':1, 'width':'100%', 'display':'block'});
340 iframe.css({'height':1, 'width':'100%', 'display':'block'});
344 iframe.attr('frameborder', 0);
341 iframe.attr('frameborder', 0);
345 iframe.attr('scrolling', 'auto');
342 iframe.attr('scrolling', 'auto');
346
343
347 // Once the iframe is loaded, the subarea is dynamically inserted
344 // Once the iframe is loaded, the subarea is dynamically inserted
348 iframe.on('load', function() {
345 iframe.on('load', function() {
349 // Workaround needed by Firefox, to properly render svg inside
346 // Workaround needed by Firefox, to properly render svg inside
350 // iframes, see http://stackoverflow.com/questions/10177190/
347 // iframes, see http://stackoverflow.com/questions/10177190/
351 // svg-dynamically-added-to-iframe-does-not-render-correctly
348 // svg-dynamically-added-to-iframe-does-not-render-correctly
352 this.contentDocument.open();
349 this.contentDocument.open();
353
350
354 // Insert the subarea into the iframe
351 // Insert the subarea into the iframe
355 // We must directly write the html. When using Jquery's append
352 // We must directly write the html. When using Jquery's append
356 // method, javascript is evaluated in the parent document and
353 // method, javascript is evaluated in the parent document and
357 // not in the iframe document. At this point, subarea doesn't
354 // not in the iframe document. At this point, subarea doesn't
358 // contain any user content.
355 // contain any user content.
359 this.contentDocument.write(subarea.html());
356 this.contentDocument.write(subarea.html());
360
357
361 this.contentDocument.close();
358 this.contentDocument.close();
362
359
363 var body = this.contentDocument.body;
360 var body = this.contentDocument.body;
364 // Adjust the iframe height automatically
361 // Adjust the iframe height automatically
365 iframe.height(body.scrollHeight + 'px');
362 iframe.height(body.scrollHeight + 'px');
366 });
363 });
367
364
368 // Elements should be appended to the inner subarea and not to the
365 // Elements should be appended to the inner subarea and not to the
369 // iframe
366 // iframe
370 iframe.append = function(that) {
367 iframe.append = function(that) {
371 subarea.append(that);
368 subarea.append(that);
372 };
369 };
373
370
374 return iframe;
371 return iframe;
375 } else {
372 } else {
376 return subarea;
373 return subarea;
377 }
374 }
378 }
375 }
379
376
380
377
381 OutputArea.prototype._append_javascript_error = function (err, element) {
378 OutputArea.prototype._append_javascript_error = function (err, element) {
382 // display a message when a javascript error occurs in display output
379 // display a message when a javascript error occurs in display output
383 var msg = "Javascript error adding output!"
380 var msg = "Javascript error adding output!"
384 if ( element === undefined ) return;
381 if ( element === undefined ) return;
385 element
382 element
386 .append($('<div/>').text(msg).addClass('js-error'))
383 .append($('<div/>').text(msg).addClass('js-error'))
387 .append($('<div/>').text(err.toString()).addClass('js-error'))
384 .append($('<div/>').text(err.toString()).addClass('js-error'))
388 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
385 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
389 };
386 };
390
387
391 OutputArea.prototype._safe_append = function (toinsert) {
388 OutputArea.prototype._safe_append = function (toinsert) {
392 // safely append an item to the document
389 // safely append an item to the document
393 // this is an object created by user code,
390 // this is an object created by user code,
394 // and may have errors, which should not be raised
391 // and may have errors, which should not be raised
395 // under any circumstances.
392 // under any circumstances.
396 try {
393 try {
397 this.element.append(toinsert);
394 this.element.append(toinsert);
398 } catch(err) {
395 } catch(err) {
399 console.log(err);
396 console.log(err);
400 // Create an actual output_area and output_subarea, which creates
397 // Create an actual output_area and output_subarea, which creates
401 // the prompt area and the proper indentation.
398 // the prompt area and the proper indentation.
402 var toinsert = this.create_output_area();
399 var toinsert = this.create_output_area();
403 var subarea = $('<div/>').addClass('output_subarea');
400 var subarea = $('<div/>').addClass('output_subarea');
404 toinsert.append(subarea);
401 toinsert.append(subarea);
405 this._append_javascript_error(err, subarea);
402 this._append_javascript_error(err, subarea);
406 this.element.append(toinsert);
403 this.element.append(toinsert);
407 }
404 }
408 };
405 };
409
406
410
407
411 OutputArea.prototype.append_pyout = function (json) {
408 OutputArea.prototype.append_pyout = function (json) {
412 var n = json.prompt_number || ' ';
409 var n = json.prompt_number || ' ';
413 var toinsert = this.create_output_area();
410 var toinsert = this.create_output_area();
414 if (this.prompt_area) {
411 if (this.prompt_area) {
415 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
412 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
416 }
413 }
417 var inserted = this.append_mime_type(json, toinsert);
414 var inserted = this.append_mime_type(json, toinsert);
418 if (inserted) {
415 if (inserted) {
419 inserted.addClass('output_pyout');
416 inserted.addClass('output_pyout');
420 }
417 }
421 this._safe_append(toinsert);
418 this._safe_append(toinsert);
422 // If we just output latex, typeset it.
419 // If we just output latex, typeset it.
423 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
420 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
424 this.typeset();
421 this.typeset();
425 }
422 }
426 };
423 };
427
424
428
425
429 OutputArea.prototype.append_pyerr = function (json) {
426 OutputArea.prototype.append_pyerr = function (json) {
430 var tb = json.traceback;
427 var tb = json.traceback;
431 if (tb !== undefined && tb.length > 0) {
428 if (tb !== undefined && tb.length > 0) {
432 var s = '';
429 var s = '';
433 var len = tb.length;
430 var len = tb.length;
434 for (var i=0; i<len; i++) {
431 for (var i=0; i<len; i++) {
435 s = s + tb[i] + '\n';
432 s = s + tb[i] + '\n';
436 }
433 }
437 s = s + '\n';
434 s = s + '\n';
438 var toinsert = this.create_output_area();
435 var toinsert = this.create_output_area();
439 var append_text = OutputArea.append_map['text/plain'];
436 var append_text = OutputArea.append_map['text/plain'];
440 if (append_text) {
437 if (append_text) {
441 append_text.apply(this, [s, {}, toinsert]).addClass('output_pyerr');
438 append_text.apply(this, [s, {}, toinsert]).addClass('output_pyerr');
442 }
439 }
443 this._safe_append(toinsert);
440 this._safe_append(toinsert);
444 }
441 }
445 };
442 };
446
443
447
444
448 OutputArea.prototype.append_stream = function (json) {
445 OutputArea.prototype.append_stream = function (json) {
449 // temporary fix: if stream undefined (json file written prior to this patch),
446 // temporary fix: if stream undefined (json file written prior to this patch),
450 // default to most likely stdout:
447 // default to most likely stdout:
451 if (json.stream === undefined){
448 if (json.stream === undefined){
452 json.stream = 'stdout';
449 json.stream = 'stdout';
453 }
450 }
454 var text = json.text;
451 var text = json.text;
455 var subclass = "output_"+json.stream;
452 var subclass = "output_"+json.stream;
456 if (this.outputs.length > 0){
453 if (this.outputs.length > 0){
457 // have at least one output to consider
454 // have at least one output to consider
458 var last = this.outputs[this.outputs.length-1];
455 var last = this.outputs[this.outputs.length-1];
459 if (last.output_type == 'stream' && json.stream == last.stream){
456 if (last.output_type == 'stream' && json.stream == last.stream){
460 // latest output was in the same stream,
457 // latest output was in the same stream,
461 // so append directly into its pre tag
458 // so append directly into its pre tag
462 // escape ANSI & HTML specials:
459 // escape ANSI & HTML specials:
463 var pre = this.element.find('div.'+subclass).last().find('pre');
460 var pre = this.element.find('div.'+subclass).last().find('pre');
464 var html = utils.fixCarriageReturn(
461 var html = utils.fixCarriageReturn(
465 pre.html() + utils.fixConsole(text));
462 pre.html() + utils.fixConsole(text));
466 // The only user content injected with this HTML call is
463 // The only user content injected with this HTML call is
467 // escaped by the fixConsole() method.
464 // escaped by the fixConsole() method.
468 pre.html(html);
465 pre.html(html);
469 return;
466 return;
470 }
467 }
471 }
468 }
472
469
473 if (!text.replace("\r", "")) {
470 if (!text.replace("\r", "")) {
474 // text is nothing (empty string, \r, etc.)
471 // text is nothing (empty string, \r, etc.)
475 // so don't append any elements, which might add undesirable space
472 // so don't append any elements, which might add undesirable space
476 return;
473 return;
477 }
474 }
478
475
479 // If we got here, attach a new div
476 // If we got here, attach a new div
480 var toinsert = this.create_output_area();
477 var toinsert = this.create_output_area();
481 var append_text = OutputArea.append_map['text/plain'];
478 var append_text = OutputArea.append_map['text/plain'];
482 if (append_text) {
479 if (append_text) {
483 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
480 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
484 }
481 }
485 this._safe_append(toinsert);
482 this._safe_append(toinsert);
486 };
483 };
487
484
488
485
489 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
486 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
490 var toinsert = this.create_output_area();
487 var toinsert = this.create_output_area();
491 if (this.append_mime_type(json, toinsert, handle_inserted)) {
488 if (this.append_mime_type(json, toinsert, handle_inserted)) {
492 this._safe_append(toinsert);
489 this._safe_append(toinsert);
493 // If we just output latex, typeset it.
490 // If we just output latex, typeset it.
494 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
491 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
495 this.typeset();
492 this.typeset();
496 }
493 }
497 }
494 }
498 };
495 };
499
496
500
497
501 OutputArea.safe_outputs = {
498 OutputArea.safe_outputs = {
502 'text/plain' : true,
499 'text/plain' : true,
503 'text/latex' : true,
500 'text/latex' : true,
504 'image/png' : true,
501 'image/png' : true,
505 'image/jpeg' : true
502 'image/jpeg' : true
506 };
503 };
507
504
508 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
505 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
509 for (var type_i in OutputArea.display_order) {
506 for (var type_i in OutputArea.display_order) {
510 var type = OutputArea.display_order[type_i];
507 var type = OutputArea.display_order[type_i];
511 var append = OutputArea.append_map[type];
508 var append = OutputArea.append_map[type];
512 if ((json[type] !== undefined) && append) {
509 if ((json[type] !== undefined) && append) {
513 var value = json[type];
510 var value = json[type];
514 if (!this.trusted && !OutputArea.safe_outputs[type]) {
511 if (!this.trusted && !OutputArea.safe_outputs[type]) {
515 // not trusted, sanitize HTML
512 // not trusted, sanitize HTML
516 if (type==='text/html' || type==='text/svg') {
513 if (type==='text/html' || type==='text/svg') {
517 value = IPython.security.sanitize_html(value);
514 value = IPython.security.sanitize_html(value);
518 } else {
515 } else {
519 // don't display if we don't know how to sanitize it
516 // don't display if we don't know how to sanitize it
520 console.log("Ignoring untrusted " + type + " output.");
517 console.log("Ignoring untrusted " + type + " output.");
521 continue;
518 continue;
522 }
519 }
523 }
520 }
524 var md = json.metadata || {};
521 var md = json.metadata || {};
525 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
522 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
526 // Since only the png and jpeg mime types call the inserted
523 // Since only the png and jpeg mime types call the inserted
527 // callback, if the mime type is something other we must call the
524 // callback, if the mime type is something other we must call the
528 // inserted callback only when the element is actually inserted
525 // inserted callback only when the element is actually inserted
529 // into the DOM. Use a timeout of 0 to do this.
526 // into the DOM. Use a timeout of 0 to do this.
530 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
527 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
531 setTimeout(handle_inserted, 0);
528 setTimeout(handle_inserted, 0);
532 }
529 }
533 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
530 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
534 return toinsert;
531 return toinsert;
535 }
532 }
536 }
533 }
537 return null;
534 return null;
538 };
535 };
539
536
540
537
541 var append_html = function (html, md, element) {
538 var append_html = function (html, md, element) {
542 var type = 'text/html';
539 var type = 'text/html';
543 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
540 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
544 IPython.keyboard_manager.register_events(toinsert);
541 IPython.keyboard_manager.register_events(toinsert);
545 toinsert.append(html);
542 toinsert.append(html);
546 element.append(toinsert);
543 element.append(toinsert);
547 return toinsert;
544 return toinsert;
548 };
545 };
549
546
550
547
551 var append_javascript = function (js, md, element) {
548 var append_javascript = function (js, md, element) {
552 // We just eval the JS code, element appears in the local scope.
549 // We just eval the JS code, element appears in the local scope.
553 var type = 'application/javascript';
550 var type = 'application/javascript';
554 var toinsert = this.create_output_subarea(md, "output_javascript", type);
551 var toinsert = this.create_output_subarea(md, "output_javascript", type);
555 IPython.keyboard_manager.register_events(toinsert);
552 IPython.keyboard_manager.register_events(toinsert);
556 element.append(toinsert);
553 element.append(toinsert);
557 // FIXME TODO : remove `container element for 3.0`
554 // FIXME TODO : remove `container element for 3.0`
558 //backward compat, js should be eval'ed in a context where `container` is defined.
555 //backward compat, js should be eval'ed in a context where `container` is defined.
559 var container = element;
556 var container = element;
560 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
557 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
561 // end backward compat
558 // end backward compat
562
559
563 // Fix for ipython/issues/5293, make sure `element` is the area which
560 // Fix for ipython/issues/5293, make sure `element` is the area which
564 // output can be inserted into at the time of JS execution.
561 // output can be inserted into at the time of JS execution.
565 element = toinsert;
562 element = toinsert;
566 try {
563 try {
567 eval(js);
564 eval(js);
568 } catch(err) {
565 } catch(err) {
569 console.log(err);
566 console.log(err);
570 this._append_javascript_error(err, toinsert);
567 this._append_javascript_error(err, toinsert);
571 }
568 }
572 return toinsert;
569 return toinsert;
573 };
570 };
574
571
575
572
576 var append_text = function (data, md, element) {
573 var append_text = function (data, md, element) {
577 var type = 'text/plain';
574 var type = 'text/plain';
578 var toinsert = this.create_output_subarea(md, "output_text", type);
575 var toinsert = this.create_output_subarea(md, "output_text", type);
579 // escape ANSI & HTML specials in plaintext:
576 // escape ANSI & HTML specials in plaintext:
580 data = utils.fixConsole(data);
577 data = utils.fixConsole(data);
581 data = utils.fixCarriageReturn(data);
578 data = utils.fixCarriageReturn(data);
582 data = utils.autoLinkUrls(data);
579 data = utils.autoLinkUrls(data);
583 // The only user content injected with this HTML call is
580 // The only user content injected with this HTML call is
584 // escaped by the fixConsole() method.
581 // escaped by the fixConsole() method.
585 toinsert.append($("<pre/>").html(data));
582 toinsert.append($("<pre/>").html(data));
586 element.append(toinsert);
583 element.append(toinsert);
587 return toinsert;
584 return toinsert;
588 };
585 };
589
586
590
587
591 var append_svg = function (svg, md, element) {
588 var append_svg = function (svg, md, element) {
592 var type = 'image/svg+xml';
589 var type = 'image/svg+xml';
593 var toinsert = this.create_output_subarea(md, "output_svg", type);
590 var toinsert = this.create_output_subarea(md, "output_svg", type);
594 toinsert.append(svg);
591 toinsert.append(svg);
595 element.append(toinsert);
592 element.append(toinsert);
596 return toinsert;
593 return toinsert;
597 };
594 };
598
595
599
596
600 OutputArea.prototype._dblclick_to_reset_size = function (img) {
597 OutputArea.prototype._dblclick_to_reset_size = function (img) {
601 // wrap image after it's loaded on the page,
598 // wrap image after it's loaded on the page,
602 // otherwise the measured initial size will be incorrect
599 // otherwise the measured initial size will be incorrect
603 img.on("load", function (){
600 img.on("load", function (){
604 var h0 = img.height();
601 var h0 = img.height();
605 var w0 = img.width();
602 var w0 = img.width();
606 if (!(h0 && w0)) {
603 if (!(h0 && w0)) {
607 // zero size, don't make it resizable
604 // zero size, don't make it resizable
608 return;
605 return;
609 }
606 }
610 img.resizable({
607 img.resizable({
611 aspectRatio: true,
608 aspectRatio: true,
612 autoHide: true
609 autoHide: true
613 });
610 });
614 img.dblclick(function () {
611 img.dblclick(function () {
615 // resize wrapper & image together for some reason:
612 // resize wrapper & image together for some reason:
616 img.parent().height(h0);
613 img.parent().height(h0);
617 img.height(h0);
614 img.height(h0);
618 img.parent().width(w0);
615 img.parent().width(w0);
619 img.width(w0);
616 img.width(w0);
620 });
617 });
621 });
618 });
622 };
619 };
623
620
624 var set_width_height = function (img, md, mime) {
621 var set_width_height = function (img, md, mime) {
625 // set width and height of an img element from metadata
622 // set width and height of an img element from metadata
626 var height = _get_metadata_key(md, 'height', mime);
623 var height = _get_metadata_key(md, 'height', mime);
627 if (height !== undefined) img.attr('height', height);
624 if (height !== undefined) img.attr('height', height);
628 var width = _get_metadata_key(md, 'width', mime);
625 var width = _get_metadata_key(md, 'width', mime);
629 if (width !== undefined) img.attr('width', width);
626 if (width !== undefined) img.attr('width', width);
630 };
627 };
631
628
632 var append_png = function (png, md, element, handle_inserted) {
629 var append_png = function (png, md, element, handle_inserted) {
633 var type = 'image/png';
630 var type = 'image/png';
634 var toinsert = this.create_output_subarea(md, "output_png", type);
631 var toinsert = this.create_output_subarea(md, "output_png", type);
635 var img = $("<img/>");
632 var img = $("<img/>");
636 if (handle_inserted !== undefined) {
633 if (handle_inserted !== undefined) {
637 img.on('load', function(){
634 img.on('load', function(){
638 handle_inserted(img);
635 handle_inserted(img);
639 });
636 });
640 }
637 }
641 img[0].src = 'data:image/png;base64,'+ png;
638 img[0].src = 'data:image/png;base64,'+ png;
642 set_width_height(img, md, 'image/png');
639 set_width_height(img, md, 'image/png');
643 this._dblclick_to_reset_size(img);
640 this._dblclick_to_reset_size(img);
644 toinsert.append(img);
641 toinsert.append(img);
645 element.append(toinsert);
642 element.append(toinsert);
646 return toinsert;
643 return toinsert;
647 };
644 };
648
645
649
646
650 var append_jpeg = function (jpeg, md, element, handle_inserted) {
647 var append_jpeg = function (jpeg, md, element, handle_inserted) {
651 var type = 'image/jpeg';
648 var type = 'image/jpeg';
652 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
649 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
653 var img = $("<img/>");
650 var img = $("<img/>");
654 if (handle_inserted !== undefined) {
651 if (handle_inserted !== undefined) {
655 img.on('load', function(){
652 img.on('load', function(){
656 handle_inserted(img);
653 handle_inserted(img);
657 });
654 });
658 }
655 }
659 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
656 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
660 set_width_height(img, md, 'image/jpeg');
657 set_width_height(img, md, 'image/jpeg');
661 this._dblclick_to_reset_size(img);
658 this._dblclick_to_reset_size(img);
662 toinsert.append(img);
659 toinsert.append(img);
663 element.append(toinsert);
660 element.append(toinsert);
664 return toinsert;
661 return toinsert;
665 };
662 };
666
663
667
664
668 var append_pdf = function (pdf, md, element) {
665 var append_pdf = function (pdf, md, element) {
669 var type = 'application/pdf';
666 var type = 'application/pdf';
670 var toinsert = this.create_output_subarea(md, "output_pdf", type);
667 var toinsert = this.create_output_subarea(md, "output_pdf", type);
671 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
668 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
672 a.attr('target', '_blank');
669 a.attr('target', '_blank');
673 a.text('View PDF')
670 a.text('View PDF')
674 toinsert.append(a);
671 toinsert.append(a);
675 element.append(toinsert);
672 element.append(toinsert);
676 return toinsert;
673 return toinsert;
677 }
674 }
678
675
679 var append_latex = function (latex, md, element) {
676 var append_latex = function (latex, md, element) {
680 // This method cannot do the typesetting because the latex first has to
677 // This method cannot do the typesetting because the latex first has to
681 // be on the page.
678 // be on the page.
682 var type = 'text/latex';
679 var type = 'text/latex';
683 var toinsert = this.create_output_subarea(md, "output_latex", type);
680 var toinsert = this.create_output_subarea(md, "output_latex", type);
684 toinsert.append(latex);
681 toinsert.append(latex);
685 element.append(toinsert);
682 element.append(toinsert);
686 return toinsert;
683 return toinsert;
687 };
684 };
688
685
689
686
690 OutputArea.prototype.append_raw_input = function (msg) {
687 OutputArea.prototype.append_raw_input = function (msg) {
691 var that = this;
688 var that = this;
692 this.expand();
689 this.expand();
693 var content = msg.content;
690 var content = msg.content;
694 var area = this.create_output_area();
691 var area = this.create_output_area();
695
692
696 // disable any other raw_inputs, if they are left around
693 // disable any other raw_inputs, if they are left around
697 $("div.output_subarea.raw_input_container").remove();
694 $("div.output_subarea.raw_input_container").remove();
698
695
699 area.append(
696 area.append(
700 $("<div/>")
697 $("<div/>")
701 .addClass("box-flex1 output_subarea raw_input_container")
698 .addClass("box-flex1 output_subarea raw_input_container")
702 .append(
699 .append(
703 $("<span/>")
700 $("<span/>")
704 .addClass("raw_input_prompt")
701 .addClass("raw_input_prompt")
705 .text(content.prompt)
702 .text(content.prompt)
706 )
703 )
707 .append(
704 .append(
708 $("<input/>")
705 $("<input/>")
709 .addClass("raw_input")
706 .addClass("raw_input")
710 .attr('type', 'text')
707 .attr('type', 'text')
711 .attr("size", 47)
708 .attr("size", 47)
712 .keydown(function (event, ui) {
709 .keydown(function (event, ui) {
713 // make sure we submit on enter,
710 // make sure we submit on enter,
714 // and don't re-execute the *cell* on shift-enter
711 // and don't re-execute the *cell* on shift-enter
715 if (event.which === IPython.keyboard.keycodes.enter) {
712 if (event.which === IPython.keyboard.keycodes.enter) {
716 that._submit_raw_input();
713 that._submit_raw_input();
717 return false;
714 return false;
718 }
715 }
719 })
716 })
720 )
717 )
721 );
718 );
722
719
723 this.element.append(area);
720 this.element.append(area);
724 var raw_input = area.find('input.raw_input');
721 var raw_input = area.find('input.raw_input');
725 // Register events that enable/disable the keyboard manager while raw
722 // Register events that enable/disable the keyboard manager while raw
726 // input is focused.
723 // input is focused.
727 IPython.keyboard_manager.register_events(raw_input);
724 IPython.keyboard_manager.register_events(raw_input);
728 // Note, the following line used to read raw_input.focus().focus().
725 // Note, the following line used to read raw_input.focus().focus().
729 // This seemed to be needed otherwise only the cell would be focused.
726 // This seemed to be needed otherwise only the cell would be focused.
730 // But with the modal UI, this seems to work fine with one call to focus().
727 // But with the modal UI, this seems to work fine with one call to focus().
731 raw_input.focus();
728 raw_input.focus();
732 }
729 }
733
730
734 OutputArea.prototype._submit_raw_input = function (evt) {
731 OutputArea.prototype._submit_raw_input = function (evt) {
735 var container = this.element.find("div.raw_input_container");
732 var container = this.element.find("div.raw_input_container");
736 var theprompt = container.find("span.raw_input_prompt");
733 var theprompt = container.find("span.raw_input_prompt");
737 var theinput = container.find("input.raw_input");
734 var theinput = container.find("input.raw_input");
738 var value = theinput.val();
735 var value = theinput.val();
739 var content = {
736 var content = {
740 output_type : 'stream',
737 output_type : 'stream',
741 name : 'stdout',
738 name : 'stdout',
742 text : theprompt.text() + value + '\n'
739 text : theprompt.text() + value + '\n'
743 }
740 }
744 // remove form container
741 // remove form container
745 container.parent().remove();
742 container.parent().remove();
746 // replace with plaintext version in stdout
743 // replace with plaintext version in stdout
747 this.append_output(content, false);
744 this.append_output(content, false);
748 $([IPython.events]).trigger('send_input_reply.Kernel', value);
745 $([IPython.events]).trigger('send_input_reply.Kernel', value);
749 }
746 }
750
747
751
748
752 OutputArea.prototype.handle_clear_output = function (msg) {
749 OutputArea.prototype.handle_clear_output = function (msg) {
753 // msg spec v4 had stdout, stderr, display keys
750 // msg spec v4 had stdout, stderr, display keys
754 // v4.1 replaced these with just wait
751 // v4.1 replaced these with just wait
755 // The default behavior is the same (stdout=stderr=display=True, wait=False),
752 // The default behavior is the same (stdout=stderr=display=True, wait=False),
756 // so v4 messages will still be properly handled,
753 // so v4 messages will still be properly handled,
757 // except for the rarely used clearing less than all output.
754 // except for the rarely used clearing less than all output.
758 this.clear_output(msg.content.wait || false);
755 this.clear_output(msg.content.wait || false);
759 };
756 };
760
757
761
758
762 OutputArea.prototype.clear_output = function(wait) {
759 OutputArea.prototype.clear_output = function(wait) {
763 if (wait) {
760 if (wait) {
764
761
765 // If a clear is queued, clear before adding another to the queue.
762 // If a clear is queued, clear before adding another to the queue.
766 if (this.clear_queued) {
763 if (this.clear_queued) {
767 this.clear_output(false);
764 this.clear_output(false);
768 };
765 };
769
766
770 this.clear_queued = true;
767 this.clear_queued = true;
771 } else {
768 } else {
772
769
773 // Fix the output div's height if the clear_output is waiting for
770 // Fix the output div's height if the clear_output is waiting for
774 // new output (it is being used in an animation).
771 // new output (it is being used in an animation).
775 if (this.clear_queued) {
772 if (this.clear_queued) {
776 var height = this.element.height();
773 var height = this.element.height();
777 this.element.height(height);
774 this.element.height(height);
778 this.clear_queued = false;
775 this.clear_queued = false;
779 }
776 }
780
777
781 // Clear all
778 // Clear all
782 // Remove load event handlers from img tags because we don't want
779 // Remove load event handlers from img tags because we don't want
783 // them to fire if the image is never added to the page.
780 // them to fire if the image is never added to the page.
784 this.element.find('img').off('load');
781 this.element.find('img').off('load');
785 this.element.html("");
782 this.element.html("");
786 this.outputs = [];
783 this.outputs = [];
787 this.trusted = true;
784 this.trusted = true;
788 this.unscroll_area();
785 this.unscroll_area();
789 return;
786 return;
790 };
787 };
791 };
788 };
792
789
793
790
794 // JSON serialization
791 // JSON serialization
795
792
796 OutputArea.prototype.fromJSON = function (outputs) {
793 OutputArea.prototype.fromJSON = function (outputs) {
797 var len = outputs.length;
794 var len = outputs.length;
798 var data;
795 var data;
799
796
800 for (var i=0; i<len; i++) {
797 for (var i=0; i<len; i++) {
801 data = outputs[i];
798 data = outputs[i];
802 var msg_type = data.output_type;
799 var msg_type = data.output_type;
803 if (msg_type === "display_data" || msg_type === "pyout") {
800 if (msg_type === "display_data" || msg_type === "pyout") {
804 // convert short keys to mime keys
801 // convert short keys to mime keys
805 // TODO: remove mapping of short keys when we update to nbformat 4
802 // TODO: remove mapping of short keys when we update to nbformat 4
806 data = this.rename_keys(data, OutputArea.mime_map_r);
803 data = this.rename_keys(data, OutputArea.mime_map_r);
807 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
804 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
808 }
805 }
809
806
810 this.append_output(data);
807 this.append_output(data);
811 }
808 }
812 };
809 };
813
810
814
811
815 OutputArea.prototype.toJSON = function () {
812 OutputArea.prototype.toJSON = function () {
816 var outputs = [];
813 var outputs = [];
817 var len = this.outputs.length;
814 var len = this.outputs.length;
818 var data;
815 var data;
819 for (var i=0; i<len; i++) {
816 for (var i=0; i<len; i++) {
820 data = this.outputs[i];
817 data = this.outputs[i];
821 var msg_type = data.output_type;
818 var msg_type = data.output_type;
822 if (msg_type === "display_data" || msg_type === "pyout") {
819 if (msg_type === "display_data" || msg_type === "pyout") {
823 // convert mime keys to short keys
820 // convert mime keys to short keys
824 data = this.rename_keys(data, OutputArea.mime_map);
821 data = this.rename_keys(data, OutputArea.mime_map);
825 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
822 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
826 }
823 }
827 outputs[i] = data;
824 outputs[i] = data;
828 }
825 }
829 return outputs;
826 return outputs;
830 };
827 };
831
828
832 /**
829 /**
833 * Class properties
830 * Class properties
834 **/
831 **/
835
832
836 /**
833 /**
837 * Threshold to trigger autoscroll when the OutputArea is resized,
834 * Threshold to trigger autoscroll when the OutputArea is resized,
838 * typically when new outputs are added.
835 * typically when new outputs are added.
839 *
836 *
840 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
837 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
841 * unless it is < 0, in which case autoscroll will never be triggered
838 * unless it is < 0, in which case autoscroll will never be triggered
842 *
839 *
843 * @property auto_scroll_threshold
840 * @property auto_scroll_threshold
844 * @type Number
841 * @type Number
845 * @default 100
842 * @default 100
846 *
843 *
847 **/
844 **/
848 OutputArea.auto_scroll_threshold = 100;
845 OutputArea.auto_scroll_threshold = 100;
849
846
850 /**
847 /**
851 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
848 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
852 * shorter than this are never scrolled.
849 * shorter than this are never scrolled.
853 *
850 *
854 * @property minimum_scroll_threshold
851 * @property minimum_scroll_threshold
855 * @type Number
852 * @type Number
856 * @default 20
853 * @default 20
857 *
854 *
858 **/
855 **/
859 OutputArea.minimum_scroll_threshold = 20;
856 OutputArea.minimum_scroll_threshold = 20;
860
857
861
858
862
859
863 OutputArea.mime_map = {
860 OutputArea.mime_map = {
864 "text/plain" : "text",
861 "text/plain" : "text",
865 "text/html" : "html",
862 "text/html" : "html",
866 "image/svg+xml" : "svg",
863 "image/svg+xml" : "svg",
867 "image/png" : "png",
864 "image/png" : "png",
868 "image/jpeg" : "jpeg",
865 "image/jpeg" : "jpeg",
869 "text/latex" : "latex",
866 "text/latex" : "latex",
870 "application/json" : "json",
867 "application/json" : "json",
871 "application/javascript" : "javascript",
868 "application/javascript" : "javascript",
872 };
869 };
873
870
874 OutputArea.mime_map_r = {
871 OutputArea.mime_map_r = {
875 "text" : "text/plain",
872 "text" : "text/plain",
876 "html" : "text/html",
873 "html" : "text/html",
877 "svg" : "image/svg+xml",
874 "svg" : "image/svg+xml",
878 "png" : "image/png",
875 "png" : "image/png",
879 "jpeg" : "image/jpeg",
876 "jpeg" : "image/jpeg",
880 "latex" : "text/latex",
877 "latex" : "text/latex",
881 "json" : "application/json",
878 "json" : "application/json",
882 "javascript" : "application/javascript",
879 "javascript" : "application/javascript",
883 };
880 };
884
881
885 OutputArea.display_order = [
882 OutputArea.display_order = [
886 'application/javascript',
883 'application/javascript',
887 'text/html',
884 'text/html',
888 'text/latex',
885 'text/latex',
889 'image/svg+xml',
886 'image/svg+xml',
890 'image/png',
887 'image/png',
891 'image/jpeg',
888 'image/jpeg',
892 'application/pdf',
889 'application/pdf',
893 'text/plain'
890 'text/plain'
894 ];
891 ];
895
892
896 OutputArea.append_map = {
893 OutputArea.append_map = {
897 "text/plain" : append_text,
894 "text/plain" : append_text,
898 "text/html" : append_html,
895 "text/html" : append_html,
899 "image/svg+xml" : append_svg,
896 "image/svg+xml" : append_svg,
900 "image/png" : append_png,
897 "image/png" : append_png,
901 "image/jpeg" : append_jpeg,
898 "image/jpeg" : append_jpeg,
902 "text/latex" : append_latex,
899 "text/latex" : append_latex,
903 "application/javascript" : append_javascript,
900 "application/javascript" : append_javascript,
904 "application/pdf" : append_pdf
901 "application/pdf" : append_pdf
905 };
902 };
906
903
907 IPython.OutputArea = OutputArea;
904 IPython.OutputArea = OutputArea;
908
905
909 return IPython;
906 return IPython;
910
907
911 }(IPython));
908 }(IPython));
@@ -1,575 +1,571 b''
1 """A ZMQ-based subclass of InteractiveShell.
1 """A ZMQ-based subclass of InteractiveShell.
2
2
3 This code is meant to ease the refactoring of the base InteractiveShell into
3 This code is meant to ease the refactoring of the base InteractiveShell into
4 something with a cleaner architecture for 2-process use, without actually
4 something with a cleaner architecture for 2-process use, without actually
5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 we subclass and override what we want to fix. Once this is working well, we
6 we subclass and override what we want to fix. Once this is working well, we
7 can go back to the base class and refactor the code for a cleaner inheritance
7 can go back to the base class and refactor the code for a cleaner inheritance
8 implementation that doesn't rely on so much monkeypatching.
8 implementation that doesn't rely on so much monkeypatching.
9
9
10 But this lets us maintain a fully working IPython as we develop the new
10 But this lets us maintain a fully working IPython as we develop the new
11 machinery. This should thus be thought of as scaffolding.
11 machinery. This should thus be thought of as scaffolding.
12 """
12 """
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib
18 # Stdlib
19 import os
19 import os
20 import sys
20 import sys
21 import time
21 import time
22
22
23 # System library imports
23 # System library imports
24 from zmq.eventloop import ioloop
24 from zmq.eventloop import ioloop
25
25
26 # Our own
26 # Our own
27 from IPython.core.interactiveshell import (
27 from IPython.core.interactiveshell import (
28 InteractiveShell, InteractiveShellABC
28 InteractiveShell, InteractiveShellABC
29 )
29 )
30 from IPython.core import page
30 from IPython.core import page
31 from IPython.core.autocall import ZMQExitAutocall
31 from IPython.core.autocall import ZMQExitAutocall
32 from IPython.core.displaypub import DisplayPublisher
32 from IPython.core.displaypub import DisplayPublisher
33 from IPython.core.error import UsageError
33 from IPython.core.error import UsageError
34 from IPython.core.magics import MacroToEdit, CodeMagics
34 from IPython.core.magics import MacroToEdit, CodeMagics
35 from IPython.core.magic import magics_class, line_magic, Magics
35 from IPython.core.magic import magics_class, line_magic, Magics
36 from IPython.core.payloadpage import install_payload_page
36 from IPython.core.payloadpage import install_payload_page
37 from IPython.display import display, Javascript
37 from IPython.display import display, Javascript
38 from IPython.kernel.inprocess.socket import SocketABC
38 from IPython.kernel.inprocess.socket import SocketABC
39 from IPython.kernel import (
39 from IPython.kernel import (
40 get_connection_file, get_connection_info, connect_qtconsole
40 get_connection_file, get_connection_info, connect_qtconsole
41 )
41 )
42 from IPython.testing.skipdoctest import skip_doctest
42 from IPython.testing.skipdoctest import skip_doctest
43 from IPython.utils import openpy
43 from IPython.utils import openpy
44 from IPython.utils.jsonutil import json_clean, encode_images
44 from IPython.utils.jsonutil import json_clean, encode_images
45 from IPython.utils.process import arg_split
45 from IPython.utils.process import arg_split
46 from IPython.utils import py3compat
46 from IPython.utils import py3compat
47 from IPython.utils.py3compat import unicode_type
47 from IPython.utils.py3compat import unicode_type
48 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes, Any
48 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes, Any
49 from IPython.utils.warn import error
49 from IPython.utils.warn import error
50 from IPython.kernel.zmq.displayhook import ZMQShellDisplayHook
50 from IPython.kernel.zmq.displayhook import ZMQShellDisplayHook
51 from IPython.kernel.zmq.datapub import ZMQDataPublisher
51 from IPython.kernel.zmq.datapub import ZMQDataPublisher
52 from IPython.kernel.zmq.session import extract_header
52 from IPython.kernel.zmq.session import extract_header
53 from IPython.kernel.comm import CommManager
53 from IPython.kernel.comm import CommManager
54 from .session import Session
54 from .session import Session
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Functions and classes
57 # Functions and classes
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 class ZMQDisplayPublisher(DisplayPublisher):
60 class ZMQDisplayPublisher(DisplayPublisher):
61 """A display publisher that publishes data using a ZeroMQ PUB socket."""
61 """A display publisher that publishes data using a ZeroMQ PUB socket."""
62
62
63 session = Instance(Session)
63 session = Instance(Session)
64 pub_socket = Instance(SocketABC)
64 pub_socket = Instance(SocketABC)
65 parent_header = Dict({})
65 parent_header = Dict({})
66 topic = CBytes(b'display_data')
66 topic = CBytes(b'display_data')
67
67
68 def set_parent(self, parent):
68 def set_parent(self, parent):
69 """Set the parent for outbound messages."""
69 """Set the parent for outbound messages."""
70 self.parent_header = extract_header(parent)
70 self.parent_header = extract_header(parent)
71
71
72 def _flush_streams(self):
72 def _flush_streams(self):
73 """flush IO Streams prior to display"""
73 """flush IO Streams prior to display"""
74 sys.stdout.flush()
74 sys.stdout.flush()
75 sys.stderr.flush()
75 sys.stderr.flush()
76
76
77 def publish(self, source, data, metadata=None):
77 def publish(self, source, data, metadata=None):
78 self._flush_streams()
78 self._flush_streams()
79 if metadata is None:
79 if metadata is None:
80 metadata = {}
80 metadata = {}
81 self._validate_data(source, data, metadata)
81 self._validate_data(source, data, metadata)
82 content = {}
82 content = {}
83 content['source'] = source
83 content['source'] = source
84 content['data'] = encode_images(data)
84 content['data'] = encode_images(data)
85 content['metadata'] = metadata
85 content['metadata'] = metadata
86 self.session.send(
86 self.session.send(
87 self.pub_socket, u'display_data', json_clean(content),
87 self.pub_socket, u'display_data', json_clean(content),
88 parent=self.parent_header, ident=self.topic,
88 parent=self.parent_header, ident=self.topic,
89 )
89 )
90
90
91 def clear_output(self, wait=False):
91 def clear_output(self, wait=False):
92 content = dict(wait=wait)
92 content = dict(wait=wait)
93
94 print('\r', file=sys.stdout, end='')
95 print('\r', file=sys.stderr, end='')
96 self._flush_streams()
93 self._flush_streams()
97
98 self.session.send(
94 self.session.send(
99 self.pub_socket, u'clear_output', content,
95 self.pub_socket, u'clear_output', content,
100 parent=self.parent_header, ident=self.topic,
96 parent=self.parent_header, ident=self.topic,
101 )
97 )
102
98
103 @magics_class
99 @magics_class
104 class KernelMagics(Magics):
100 class KernelMagics(Magics):
105 #------------------------------------------------------------------------
101 #------------------------------------------------------------------------
106 # Magic overrides
102 # Magic overrides
107 #------------------------------------------------------------------------
103 #------------------------------------------------------------------------
108 # Once the base class stops inheriting from magic, this code needs to be
104 # Once the base class stops inheriting from magic, this code needs to be
109 # moved into a separate machinery as well. For now, at least isolate here
105 # moved into a separate machinery as well. For now, at least isolate here
110 # the magics which this class needs to implement differently from the base
106 # the magics which this class needs to implement differently from the base
111 # class, or that are unique to it.
107 # class, or that are unique to it.
112
108
113 @line_magic
109 @line_magic
114 def doctest_mode(self, parameter_s=''):
110 def doctest_mode(self, parameter_s=''):
115 """Toggle doctest mode on and off.
111 """Toggle doctest mode on and off.
116
112
117 This mode is intended to make IPython behave as much as possible like a
113 This mode is intended to make IPython behave as much as possible like a
118 plain Python shell, from the perspective of how its prompts, exceptions
114 plain Python shell, from the perspective of how its prompts, exceptions
119 and output look. This makes it easy to copy and paste parts of a
115 and output look. This makes it easy to copy and paste parts of a
120 session into doctests. It does so by:
116 session into doctests. It does so by:
121
117
122 - Changing the prompts to the classic ``>>>`` ones.
118 - Changing the prompts to the classic ``>>>`` ones.
123 - Changing the exception reporting mode to 'Plain'.
119 - Changing the exception reporting mode to 'Plain'.
124 - Disabling pretty-printing of output.
120 - Disabling pretty-printing of output.
125
121
126 Note that IPython also supports the pasting of code snippets that have
122 Note that IPython also supports the pasting of code snippets that have
127 leading '>>>' and '...' prompts in them. This means that you can paste
123 leading '>>>' and '...' prompts in them. This means that you can paste
128 doctests from files or docstrings (even if they have leading
124 doctests from files or docstrings (even if they have leading
129 whitespace), and the code will execute correctly. You can then use
125 whitespace), and the code will execute correctly. You can then use
130 '%history -t' to see the translated history; this will give you the
126 '%history -t' to see the translated history; this will give you the
131 input after removal of all the leading prompts and whitespace, which
127 input after removal of all the leading prompts and whitespace, which
132 can be pasted back into an editor.
128 can be pasted back into an editor.
133
129
134 With these features, you can switch into this mode easily whenever you
130 With these features, you can switch into this mode easily whenever you
135 need to do testing and changes to doctests, without having to leave
131 need to do testing and changes to doctests, without having to leave
136 your existing IPython session.
132 your existing IPython session.
137 """
133 """
138
134
139 from IPython.utils.ipstruct import Struct
135 from IPython.utils.ipstruct import Struct
140
136
141 # Shorthands
137 # Shorthands
142 shell = self.shell
138 shell = self.shell
143 disp_formatter = self.shell.display_formatter
139 disp_formatter = self.shell.display_formatter
144 ptformatter = disp_formatter.formatters['text/plain']
140 ptformatter = disp_formatter.formatters['text/plain']
145 # dstore is a data store kept in the instance metadata bag to track any
141 # dstore is a data store kept in the instance metadata bag to track any
146 # changes we make, so we can undo them later.
142 # changes we make, so we can undo them later.
147 dstore = shell.meta.setdefault('doctest_mode', Struct())
143 dstore = shell.meta.setdefault('doctest_mode', Struct())
148 save_dstore = dstore.setdefault
144 save_dstore = dstore.setdefault
149
145
150 # save a few values we'll need to recover later
146 # save a few values we'll need to recover later
151 mode = save_dstore('mode', False)
147 mode = save_dstore('mode', False)
152 save_dstore('rc_pprint', ptformatter.pprint)
148 save_dstore('rc_pprint', ptformatter.pprint)
153 save_dstore('rc_active_types',disp_formatter.active_types)
149 save_dstore('rc_active_types',disp_formatter.active_types)
154 save_dstore('xmode', shell.InteractiveTB.mode)
150 save_dstore('xmode', shell.InteractiveTB.mode)
155
151
156 if mode == False:
152 if mode == False:
157 # turn on
153 # turn on
158 ptformatter.pprint = False
154 ptformatter.pprint = False
159 disp_formatter.active_types = ['text/plain']
155 disp_formatter.active_types = ['text/plain']
160 shell.magic('xmode Plain')
156 shell.magic('xmode Plain')
161 else:
157 else:
162 # turn off
158 # turn off
163 ptformatter.pprint = dstore.rc_pprint
159 ptformatter.pprint = dstore.rc_pprint
164 disp_formatter.active_types = dstore.rc_active_types
160 disp_formatter.active_types = dstore.rc_active_types
165 shell.magic("xmode " + dstore.xmode)
161 shell.magic("xmode " + dstore.xmode)
166
162
167 # Store new mode and inform on console
163 # Store new mode and inform on console
168 dstore.mode = bool(1-int(mode))
164 dstore.mode = bool(1-int(mode))
169 mode_label = ['OFF','ON'][dstore.mode]
165 mode_label = ['OFF','ON'][dstore.mode]
170 print('Doctest mode is:', mode_label)
166 print('Doctest mode is:', mode_label)
171
167
172 # Send the payload back so that clients can modify their prompt display
168 # Send the payload back so that clients can modify their prompt display
173 payload = dict(
169 payload = dict(
174 source='doctest_mode',
170 source='doctest_mode',
175 mode=dstore.mode)
171 mode=dstore.mode)
176 shell.payload_manager.write_payload(payload)
172 shell.payload_manager.write_payload(payload)
177
173
178
174
179 _find_edit_target = CodeMagics._find_edit_target
175 _find_edit_target = CodeMagics._find_edit_target
180
176
181 @skip_doctest
177 @skip_doctest
182 @line_magic
178 @line_magic
183 def edit(self, parameter_s='', last_call=['','']):
179 def edit(self, parameter_s='', last_call=['','']):
184 """Bring up an editor and execute the resulting code.
180 """Bring up an editor and execute the resulting code.
185
181
186 Usage:
182 Usage:
187 %edit [options] [args]
183 %edit [options] [args]
188
184
189 %edit runs an external text editor. You will need to set the command for
185 %edit runs an external text editor. You will need to set the command for
190 this editor via the ``TerminalInteractiveShell.editor`` option in your
186 this editor via the ``TerminalInteractiveShell.editor`` option in your
191 configuration file before it will work.
187 configuration file before it will work.
192
188
193 This command allows you to conveniently edit multi-line code right in
189 This command allows you to conveniently edit multi-line code right in
194 your IPython session.
190 your IPython session.
195
191
196 If called without arguments, %edit opens up an empty editor with a
192 If called without arguments, %edit opens up an empty editor with a
197 temporary file and will execute the contents of this file when you
193 temporary file and will execute the contents of this file when you
198 close it (don't forget to save it!).
194 close it (don't forget to save it!).
199
195
200 Options:
196 Options:
201
197
202 -n <number>
198 -n <number>
203 Open the editor at a specified line number. By default, the IPython
199 Open the editor at a specified line number. By default, the IPython
204 editor hook uses the unix syntax 'editor +N filename', but you can
200 editor hook uses the unix syntax 'editor +N filename', but you can
205 configure this by providing your own modified hook if your favorite
201 configure this by providing your own modified hook if your favorite
206 editor supports line-number specifications with a different syntax.
202 editor supports line-number specifications with a different syntax.
207
203
208 -p
204 -p
209 Call the editor with the same data as the previous time it was used,
205 Call the editor with the same data as the previous time it was used,
210 regardless of how long ago (in your current session) it was.
206 regardless of how long ago (in your current session) it was.
211
207
212 -r
208 -r
213 Use 'raw' input. This option only applies to input taken from the
209 Use 'raw' input. This option only applies to input taken from the
214 user's history. By default, the 'processed' history is used, so that
210 user's history. By default, the 'processed' history is used, so that
215 magics are loaded in their transformed version to valid Python. If
211 magics are loaded in their transformed version to valid Python. If
216 this option is given, the raw input as typed as the command line is
212 this option is given, the raw input as typed as the command line is
217 used instead. When you exit the editor, it will be executed by
213 used instead. When you exit the editor, it will be executed by
218 IPython's own processor.
214 IPython's own processor.
219
215
220 Arguments:
216 Arguments:
221
217
222 If arguments are given, the following possibilites exist:
218 If arguments are given, the following possibilites exist:
223
219
224 - The arguments are numbers or pairs of colon-separated numbers (like
220 - The arguments are numbers or pairs of colon-separated numbers (like
225 1 4:8 9). These are interpreted as lines of previous input to be
221 1 4:8 9). These are interpreted as lines of previous input to be
226 loaded into the editor. The syntax is the same of the %macro command.
222 loaded into the editor. The syntax is the same of the %macro command.
227
223
228 - If the argument doesn't start with a number, it is evaluated as a
224 - If the argument doesn't start with a number, it is evaluated as a
229 variable and its contents loaded into the editor. You can thus edit
225 variable and its contents loaded into the editor. You can thus edit
230 any string which contains python code (including the result of
226 any string which contains python code (including the result of
231 previous edits).
227 previous edits).
232
228
233 - If the argument is the name of an object (other than a string),
229 - If the argument is the name of an object (other than a string),
234 IPython will try to locate the file where it was defined and open the
230 IPython will try to locate the file where it was defined and open the
235 editor at the point where it is defined. You can use ``%edit function``
231 editor at the point where it is defined. You can use ``%edit function``
236 to load an editor exactly at the point where 'function' is defined,
232 to load an editor exactly at the point where 'function' is defined,
237 edit it and have the file be executed automatically.
233 edit it and have the file be executed automatically.
238
234
239 If the object is a macro (see %macro for details), this opens up your
235 If the object is a macro (see %macro for details), this opens up your
240 specified editor with a temporary file containing the macro's data.
236 specified editor with a temporary file containing the macro's data.
241 Upon exit, the macro is reloaded with the contents of the file.
237 Upon exit, the macro is reloaded with the contents of the file.
242
238
243 Note: opening at an exact line is only supported under Unix, and some
239 Note: opening at an exact line is only supported under Unix, and some
244 editors (like kedit and gedit up to Gnome 2.8) do not understand the
240 editors (like kedit and gedit up to Gnome 2.8) do not understand the
245 '+NUMBER' parameter necessary for this feature. Good editors like
241 '+NUMBER' parameter necessary for this feature. Good editors like
246 (X)Emacs, vi, jed, pico and joe all do.
242 (X)Emacs, vi, jed, pico and joe all do.
247
243
248 - If the argument is not found as a variable, IPython will look for a
244 - If the argument is not found as a variable, IPython will look for a
249 file with that name (adding .py if necessary) and load it into the
245 file with that name (adding .py if necessary) and load it into the
250 editor. It will execute its contents with execfile() when you exit,
246 editor. It will execute its contents with execfile() when you exit,
251 loading any code in the file into your interactive namespace.
247 loading any code in the file into your interactive namespace.
252
248
253 Unlike in the terminal, this is designed to use a GUI editor, and we do
249 Unlike in the terminal, this is designed to use a GUI editor, and we do
254 not know when it has closed. So the file you edit will not be
250 not know when it has closed. So the file you edit will not be
255 automatically executed or printed.
251 automatically executed or printed.
256
252
257 Note that %edit is also available through the alias %ed.
253 Note that %edit is also available through the alias %ed.
258 """
254 """
259
255
260 opts,args = self.parse_options(parameter_s,'prn:')
256 opts,args = self.parse_options(parameter_s,'prn:')
261
257
262 try:
258 try:
263 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
259 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
264 except MacroToEdit as e:
260 except MacroToEdit as e:
265 # TODO: Implement macro editing over 2 processes.
261 # TODO: Implement macro editing over 2 processes.
266 print("Macro editing not yet implemented in 2-process model.")
262 print("Macro editing not yet implemented in 2-process model.")
267 return
263 return
268
264
269 # Make sure we send to the client an absolute path, in case the working
265 # Make sure we send to the client an absolute path, in case the working
270 # directory of client and kernel don't match
266 # directory of client and kernel don't match
271 filename = os.path.abspath(filename)
267 filename = os.path.abspath(filename)
272
268
273 payload = {
269 payload = {
274 'source' : 'edit_magic',
270 'source' : 'edit_magic',
275 'filename' : filename,
271 'filename' : filename,
276 'line_number' : lineno
272 'line_number' : lineno
277 }
273 }
278 self.shell.payload_manager.write_payload(payload)
274 self.shell.payload_manager.write_payload(payload)
279
275
280 # A few magics that are adapted to the specifics of using pexpect and a
276 # A few magics that are adapted to the specifics of using pexpect and a
281 # remote terminal
277 # remote terminal
282
278
283 @line_magic
279 @line_magic
284 def clear(self, arg_s):
280 def clear(self, arg_s):
285 """Clear the terminal."""
281 """Clear the terminal."""
286 if os.name == 'posix':
282 if os.name == 'posix':
287 self.shell.system("clear")
283 self.shell.system("clear")
288 else:
284 else:
289 self.shell.system("cls")
285 self.shell.system("cls")
290
286
291 if os.name == 'nt':
287 if os.name == 'nt':
292 # This is the usual name in windows
288 # This is the usual name in windows
293 cls = line_magic('cls')(clear)
289 cls = line_magic('cls')(clear)
294
290
295 # Terminal pagers won't work over pexpect, but we do have our own pager
291 # Terminal pagers won't work over pexpect, but we do have our own pager
296
292
297 @line_magic
293 @line_magic
298 def less(self, arg_s):
294 def less(self, arg_s):
299 """Show a file through the pager.
295 """Show a file through the pager.
300
296
301 Files ending in .py are syntax-highlighted."""
297 Files ending in .py are syntax-highlighted."""
302 if not arg_s:
298 if not arg_s:
303 raise UsageError('Missing filename.')
299 raise UsageError('Missing filename.')
304
300
305 cont = open(arg_s).read()
301 cont = open(arg_s).read()
306 if arg_s.endswith('.py'):
302 if arg_s.endswith('.py'):
307 cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False))
303 cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False))
308 else:
304 else:
309 cont = open(arg_s).read()
305 cont = open(arg_s).read()
310 page.page(cont)
306 page.page(cont)
311
307
312 more = line_magic('more')(less)
308 more = line_magic('more')(less)
313
309
314 # Man calls a pager, so we also need to redefine it
310 # Man calls a pager, so we also need to redefine it
315 if os.name == 'posix':
311 if os.name == 'posix':
316 @line_magic
312 @line_magic
317 def man(self, arg_s):
313 def man(self, arg_s):
318 """Find the man page for the given command and display in pager."""
314 """Find the man page for the given command and display in pager."""
319 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
315 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
320 split=False))
316 split=False))
321
317
322 @line_magic
318 @line_magic
323 def connect_info(self, arg_s):
319 def connect_info(self, arg_s):
324 """Print information for connecting other clients to this kernel
320 """Print information for connecting other clients to this kernel
325
321
326 It will print the contents of this session's connection file, as well as
322 It will print the contents of this session's connection file, as well as
327 shortcuts for local clients.
323 shortcuts for local clients.
328
324
329 In the simplest case, when called from the most recently launched kernel,
325 In the simplest case, when called from the most recently launched kernel,
330 secondary clients can be connected, simply with:
326 secondary clients can be connected, simply with:
331
327
332 $> ipython <app> --existing
328 $> ipython <app> --existing
333
329
334 """
330 """
335
331
336 from IPython.core.application import BaseIPythonApplication as BaseIPApp
332 from IPython.core.application import BaseIPythonApplication as BaseIPApp
337
333
338 if BaseIPApp.initialized():
334 if BaseIPApp.initialized():
339 app = BaseIPApp.instance()
335 app = BaseIPApp.instance()
340 security_dir = app.profile_dir.security_dir
336 security_dir = app.profile_dir.security_dir
341 profile = app.profile
337 profile = app.profile
342 else:
338 else:
343 profile = 'default'
339 profile = 'default'
344 security_dir = ''
340 security_dir = ''
345
341
346 try:
342 try:
347 connection_file = get_connection_file()
343 connection_file = get_connection_file()
348 info = get_connection_info(unpack=False)
344 info = get_connection_info(unpack=False)
349 except Exception as e:
345 except Exception as e:
350 error("Could not get connection info: %r" % e)
346 error("Could not get connection info: %r" % e)
351 return
347 return
352
348
353 # add profile flag for non-default profile
349 # add profile flag for non-default profile
354 profile_flag = "--profile %s" % profile if profile != 'default' else ""
350 profile_flag = "--profile %s" % profile if profile != 'default' else ""
355
351
356 # if it's in the security dir, truncate to basename
352 # if it's in the security dir, truncate to basename
357 if security_dir == os.path.dirname(connection_file):
353 if security_dir == os.path.dirname(connection_file):
358 connection_file = os.path.basename(connection_file)
354 connection_file = os.path.basename(connection_file)
359
355
360
356
361 print (info + '\n')
357 print (info + '\n')
362 print ("Paste the above JSON into a file, and connect with:\n"
358 print ("Paste the above JSON into a file, and connect with:\n"
363 " $> ipython <app> --existing <file>\n"
359 " $> ipython <app> --existing <file>\n"
364 "or, if you are local, you can connect with just:\n"
360 "or, if you are local, you can connect with just:\n"
365 " $> ipython <app> --existing {0} {1}\n"
361 " $> ipython <app> --existing {0} {1}\n"
366 "or even just:\n"
362 "or even just:\n"
367 " $> ipython <app> --existing {1}\n"
363 " $> ipython <app> --existing {1}\n"
368 "if this is the most recent IPython session you have started.".format(
364 "if this is the most recent IPython session you have started.".format(
369 connection_file, profile_flag
365 connection_file, profile_flag
370 )
366 )
371 )
367 )
372
368
373 @line_magic
369 @line_magic
374 def qtconsole(self, arg_s):
370 def qtconsole(self, arg_s):
375 """Open a qtconsole connected to this kernel.
371 """Open a qtconsole connected to this kernel.
376
372
377 Useful for connecting a qtconsole to running notebooks, for better
373 Useful for connecting a qtconsole to running notebooks, for better
378 debugging.
374 debugging.
379 """
375 """
380
376
381 # %qtconsole should imply bind_kernel for engines:
377 # %qtconsole should imply bind_kernel for engines:
382 try:
378 try:
383 from IPython.parallel import bind_kernel
379 from IPython.parallel import bind_kernel
384 except ImportError:
380 except ImportError:
385 # technically possible, because parallel has higher pyzmq min-version
381 # technically possible, because parallel has higher pyzmq min-version
386 pass
382 pass
387 else:
383 else:
388 bind_kernel()
384 bind_kernel()
389
385
390 try:
386 try:
391 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
387 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
392 except Exception as e:
388 except Exception as e:
393 error("Could not start qtconsole: %r" % e)
389 error("Could not start qtconsole: %r" % e)
394 return
390 return
395
391
396 @line_magic
392 @line_magic
397 def autosave(self, arg_s):
393 def autosave(self, arg_s):
398 """Set the autosave interval in the notebook (in seconds).
394 """Set the autosave interval in the notebook (in seconds).
399
395
400 The default value is 120, or two minutes.
396 The default value is 120, or two minutes.
401 ``%autosave 0`` will disable autosave.
397 ``%autosave 0`` will disable autosave.
402
398
403 This magic only has an effect when called from the notebook interface.
399 This magic only has an effect when called from the notebook interface.
404 It has no effect when called in a startup file.
400 It has no effect when called in a startup file.
405 """
401 """
406
402
407 try:
403 try:
408 interval = int(arg_s)
404 interval = int(arg_s)
409 except ValueError:
405 except ValueError:
410 raise UsageError("%%autosave requires an integer, got %r" % arg_s)
406 raise UsageError("%%autosave requires an integer, got %r" % arg_s)
411
407
412 # javascript wants milliseconds
408 # javascript wants milliseconds
413 milliseconds = 1000 * interval
409 milliseconds = 1000 * interval
414 display(Javascript("IPython.notebook.set_autosave_interval(%i)" % milliseconds),
410 display(Javascript("IPython.notebook.set_autosave_interval(%i)" % milliseconds),
415 include=['application/javascript']
411 include=['application/javascript']
416 )
412 )
417 if interval:
413 if interval:
418 print("Autosaving every %i seconds" % interval)
414 print("Autosaving every %i seconds" % interval)
419 else:
415 else:
420 print("Autosave disabled")
416 print("Autosave disabled")
421
417
422
418
423 class ZMQInteractiveShell(InteractiveShell):
419 class ZMQInteractiveShell(InteractiveShell):
424 """A subclass of InteractiveShell for ZMQ."""
420 """A subclass of InteractiveShell for ZMQ."""
425
421
426 displayhook_class = Type(ZMQShellDisplayHook)
422 displayhook_class = Type(ZMQShellDisplayHook)
427 display_pub_class = Type(ZMQDisplayPublisher)
423 display_pub_class = Type(ZMQDisplayPublisher)
428 data_pub_class = Type(ZMQDataPublisher)
424 data_pub_class = Type(ZMQDataPublisher)
429 kernel = Any()
425 kernel = Any()
430 parent_header = Any()
426 parent_header = Any()
431
427
432 # Override the traitlet in the parent class, because there's no point using
428 # Override the traitlet in the parent class, because there's no point using
433 # readline for the kernel. Can be removed when the readline code is moved
429 # readline for the kernel. Can be removed when the readline code is moved
434 # to the terminal frontend.
430 # to the terminal frontend.
435 colors_force = CBool(True)
431 colors_force = CBool(True)
436 readline_use = CBool(False)
432 readline_use = CBool(False)
437 # autoindent has no meaning in a zmqshell, and attempting to enable it
433 # autoindent has no meaning in a zmqshell, and attempting to enable it
438 # will print a warning in the absence of readline.
434 # will print a warning in the absence of readline.
439 autoindent = CBool(False)
435 autoindent = CBool(False)
440
436
441 exiter = Instance(ZMQExitAutocall)
437 exiter = Instance(ZMQExitAutocall)
442 def _exiter_default(self):
438 def _exiter_default(self):
443 return ZMQExitAutocall(self)
439 return ZMQExitAutocall(self)
444
440
445 def _exit_now_changed(self, name, old, new):
441 def _exit_now_changed(self, name, old, new):
446 """stop eventloop when exit_now fires"""
442 """stop eventloop when exit_now fires"""
447 if new:
443 if new:
448 loop = ioloop.IOLoop.instance()
444 loop = ioloop.IOLoop.instance()
449 loop.add_timeout(time.time()+0.1, loop.stop)
445 loop.add_timeout(time.time()+0.1, loop.stop)
450
446
451 keepkernel_on_exit = None
447 keepkernel_on_exit = None
452
448
453 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
449 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
454 # interactive input being read; we provide event loop support in ipkernel
450 # interactive input being read; we provide event loop support in ipkernel
455 @staticmethod
451 @staticmethod
456 def enable_gui(gui):
452 def enable_gui(gui):
457 from .eventloops import enable_gui as real_enable_gui
453 from .eventloops import enable_gui as real_enable_gui
458 try:
454 try:
459 real_enable_gui(gui)
455 real_enable_gui(gui)
460 except ValueError as e:
456 except ValueError as e:
461 raise UsageError("%s" % e)
457 raise UsageError("%s" % e)
462
458
463 def init_environment(self):
459 def init_environment(self):
464 """Configure the user's environment.
460 """Configure the user's environment.
465
461
466 """
462 """
467 env = os.environ
463 env = os.environ
468 # These two ensure 'ls' produces nice coloring on BSD-derived systems
464 # These two ensure 'ls' produces nice coloring on BSD-derived systems
469 env['TERM'] = 'xterm-color'
465 env['TERM'] = 'xterm-color'
470 env['CLICOLOR'] = '1'
466 env['CLICOLOR'] = '1'
471 # Since normal pagers don't work at all (over pexpect we don't have
467 # Since normal pagers don't work at all (over pexpect we don't have
472 # single-key control of the subprocess), try to disable paging in
468 # single-key control of the subprocess), try to disable paging in
473 # subprocesses as much as possible.
469 # subprocesses as much as possible.
474 env['PAGER'] = 'cat'
470 env['PAGER'] = 'cat'
475 env['GIT_PAGER'] = 'cat'
471 env['GIT_PAGER'] = 'cat'
476
472
477 # And install the payload version of page.
473 # And install the payload version of page.
478 install_payload_page()
474 install_payload_page()
479
475
480 def auto_rewrite_input(self, cmd):
476 def auto_rewrite_input(self, cmd):
481 """Called to show the auto-rewritten input for autocall and friends.
477 """Called to show the auto-rewritten input for autocall and friends.
482
478
483 FIXME: this payload is currently not correctly processed by the
479 FIXME: this payload is currently not correctly processed by the
484 frontend.
480 frontend.
485 """
481 """
486 new = self.prompt_manager.render('rewrite') + cmd
482 new = self.prompt_manager.render('rewrite') + cmd
487 payload = dict(
483 payload = dict(
488 source='auto_rewrite_input',
484 source='auto_rewrite_input',
489 transformed_input=new,
485 transformed_input=new,
490 )
486 )
491 self.payload_manager.write_payload(payload)
487 self.payload_manager.write_payload(payload)
492
488
493 def ask_exit(self):
489 def ask_exit(self):
494 """Engage the exit actions."""
490 """Engage the exit actions."""
495 self.exit_now = True
491 self.exit_now = True
496 payload = dict(
492 payload = dict(
497 source='ask_exit',
493 source='ask_exit',
498 exit=True,
494 exit=True,
499 keepkernel=self.keepkernel_on_exit,
495 keepkernel=self.keepkernel_on_exit,
500 )
496 )
501 self.payload_manager.write_payload(payload)
497 self.payload_manager.write_payload(payload)
502
498
503 def _showtraceback(self, etype, evalue, stb):
499 def _showtraceback(self, etype, evalue, stb):
504 # try to preserve ordering of tracebacks and print statements
500 # try to preserve ordering of tracebacks and print statements
505 sys.stdout.flush()
501 sys.stdout.flush()
506 sys.stderr.flush()
502 sys.stderr.flush()
507
503
508 exc_content = {
504 exc_content = {
509 u'traceback' : stb,
505 u'traceback' : stb,
510 u'ename' : unicode_type(etype.__name__),
506 u'ename' : unicode_type(etype.__name__),
511 u'evalue' : py3compat.safe_unicode(evalue),
507 u'evalue' : py3compat.safe_unicode(evalue),
512 }
508 }
513
509
514 dh = self.displayhook
510 dh = self.displayhook
515 # Send exception info over pub socket for other clients than the caller
511 # Send exception info over pub socket for other clients than the caller
516 # to pick up
512 # to pick up
517 topic = None
513 topic = None
518 if dh.topic:
514 if dh.topic:
519 topic = dh.topic.replace(b'pyout', b'pyerr')
515 topic = dh.topic.replace(b'pyout', b'pyerr')
520
516
521 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
517 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
522
518
523 # FIXME - Hack: store exception info in shell object. Right now, the
519 # FIXME - Hack: store exception info in shell object. Right now, the
524 # caller is reading this info after the fact, we need to fix this logic
520 # caller is reading this info after the fact, we need to fix this logic
525 # to remove this hack. Even uglier, we need to store the error status
521 # to remove this hack. Even uglier, we need to store the error status
526 # here, because in the main loop, the logic that sets it is being
522 # here, because in the main loop, the logic that sets it is being
527 # skipped because runlines swallows the exceptions.
523 # skipped because runlines swallows the exceptions.
528 exc_content[u'status'] = u'error'
524 exc_content[u'status'] = u'error'
529 self._reply_content = exc_content
525 self._reply_content = exc_content
530 # /FIXME
526 # /FIXME
531
527
532 return exc_content
528 return exc_content
533
529
534 def set_next_input(self, text):
530 def set_next_input(self, text):
535 """Send the specified text to the frontend to be presented at the next
531 """Send the specified text to the frontend to be presented at the next
536 input cell."""
532 input cell."""
537 payload = dict(
533 payload = dict(
538 source='set_next_input',
534 source='set_next_input',
539 text=text
535 text=text
540 )
536 )
541 self.payload_manager.write_payload(payload)
537 self.payload_manager.write_payload(payload)
542
538
543 def set_parent(self, parent):
539 def set_parent(self, parent):
544 """Set the parent header for associating output with its triggering input"""
540 """Set the parent header for associating output with its triggering input"""
545 self.parent_header = parent
541 self.parent_header = parent
546 self.displayhook.set_parent(parent)
542 self.displayhook.set_parent(parent)
547 self.display_pub.set_parent(parent)
543 self.display_pub.set_parent(parent)
548 self.data_pub.set_parent(parent)
544 self.data_pub.set_parent(parent)
549 try:
545 try:
550 sys.stdout.set_parent(parent)
546 sys.stdout.set_parent(parent)
551 except AttributeError:
547 except AttributeError:
552 pass
548 pass
553 try:
549 try:
554 sys.stderr.set_parent(parent)
550 sys.stderr.set_parent(parent)
555 except AttributeError:
551 except AttributeError:
556 pass
552 pass
557
553
558 def get_parent(self):
554 def get_parent(self):
559 return self.parent_header
555 return self.parent_header
560
556
561 #-------------------------------------------------------------------------
557 #-------------------------------------------------------------------------
562 # Things related to magics
558 # Things related to magics
563 #-------------------------------------------------------------------------
559 #-------------------------------------------------------------------------
564
560
565 def init_magics(self):
561 def init_magics(self):
566 super(ZMQInteractiveShell, self).init_magics()
562 super(ZMQInteractiveShell, self).init_magics()
567 self.register_magics(KernelMagics)
563 self.register_magics(KernelMagics)
568 self.magics_manager.register_alias('ed', 'edit')
564 self.magics_manager.register_alias('ed', 'edit')
569
565
570 def init_comms(self):
566 def init_comms(self):
571 self.comm_manager = CommManager(shell=self, parent=self)
567 self.comm_manager = CommManager(shell=self, parent=self)
572 self.configurables.append(self.comm_manager)
568 self.configurables.append(self.comm_manager)
573
569
574
570
575 InteractiveShellABC.register(ZMQInteractiveShell)
571 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now