##// END OF EJS Templates
support password in input_request
MinRK -
Show More
@@ -1,965 +1,972
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 //============================================================================
4 //============================================================================
5 // OutputArea
5 // OutputArea
6 //============================================================================
6 //============================================================================
7
7
8 /**
8 /**
9 * @module IPython
9 * @module IPython
10 * @namespace IPython
10 * @namespace IPython
11 * @submodule OutputArea
11 * @submodule OutputArea
12 */
12 */
13 var IPython = (function (IPython) {
13 var IPython = (function (IPython) {
14 "use strict";
14 "use strict";
15
15
16 var utils = IPython.utils;
16 var utils = IPython.utils;
17
17
18 /**
18 /**
19 * @class OutputArea
19 * @class OutputArea
20 *
20 *
21 * @constructor
21 * @constructor
22 */
22 */
23
23
24 var OutputArea = function (selector, prompt_area) {
24 var OutputArea = function (selector, prompt_area) {
25 this.selector = selector;
25 this.selector = selector;
26 this.wrapper = $(selector);
26 this.wrapper = $(selector);
27 this.outputs = [];
27 this.outputs = [];
28 this.collapsed = false;
28 this.collapsed = false;
29 this.scrolled = false;
29 this.scrolled = false;
30 this.trusted = true;
30 this.trusted = true;
31 this.clear_queued = null;
31 this.clear_queued = null;
32 if (prompt_area === undefined) {
32 if (prompt_area === undefined) {
33 this.prompt_area = true;
33 this.prompt_area = true;
34 } else {
34 } else {
35 this.prompt_area = prompt_area;
35 this.prompt_area = prompt_area;
36 }
36 }
37 this.create_elements();
37 this.create_elements();
38 this.style();
38 this.style();
39 this.bind_events();
39 this.bind_events();
40 };
40 };
41
41
42
42
43 /**
43 /**
44 * Class prototypes
44 * Class prototypes
45 **/
45 **/
46
46
47 OutputArea.prototype.create_elements = function () {
47 OutputArea.prototype.create_elements = function () {
48 this.element = $("<div/>");
48 this.element = $("<div/>");
49 this.collapse_button = $("<div/>");
49 this.collapse_button = $("<div/>");
50 this.prompt_overlay = $("<div/>");
50 this.prompt_overlay = $("<div/>");
51 this.wrapper.append(this.prompt_overlay);
51 this.wrapper.append(this.prompt_overlay);
52 this.wrapper.append(this.element);
52 this.wrapper.append(this.element);
53 this.wrapper.append(this.collapse_button);
53 this.wrapper.append(this.collapse_button);
54 };
54 };
55
55
56
56
57 OutputArea.prototype.style = function () {
57 OutputArea.prototype.style = function () {
58 this.collapse_button.hide();
58 this.collapse_button.hide();
59 this.prompt_overlay.hide();
59 this.prompt_overlay.hide();
60
60
61 this.wrapper.addClass('output_wrapper');
61 this.wrapper.addClass('output_wrapper');
62 this.element.addClass('output');
62 this.element.addClass('output');
63
63
64 this.collapse_button.addClass("btn output_collapsed");
64 this.collapse_button.addClass("btn output_collapsed");
65 this.collapse_button.attr('title', 'click to expand output');
65 this.collapse_button.attr('title', 'click to expand output');
66 this.collapse_button.text('. . .');
66 this.collapse_button.text('. . .');
67
67
68 this.prompt_overlay.addClass('out_prompt_overlay prompt');
68 this.prompt_overlay.addClass('out_prompt_overlay prompt');
69 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
69 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
70
70
71 this.collapse();
71 this.collapse();
72 };
72 };
73
73
74 /**
74 /**
75 * Should the OutputArea scroll?
75 * Should the OutputArea scroll?
76 * Returns whether the height (in lines) exceeds a threshold.
76 * Returns whether the height (in lines) exceeds a threshold.
77 *
77 *
78 * @private
78 * @private
79 * @method _should_scroll
79 * @method _should_scroll
80 * @param [lines=100]{Integer}
80 * @param [lines=100]{Integer}
81 * @return {Bool}
81 * @return {Bool}
82 *
82 *
83 */
83 */
84 OutputArea.prototype._should_scroll = function (lines) {
84 OutputArea.prototype._should_scroll = function (lines) {
85 if (lines <=0 ){ return }
85 if (lines <=0 ){ return }
86 if (!lines) {
86 if (!lines) {
87 lines = 100;
87 lines = 100;
88 }
88 }
89 // line-height from http://stackoverflow.com/questions/1185151
89 // line-height from http://stackoverflow.com/questions/1185151
90 var fontSize = this.element.css('font-size');
90 var fontSize = this.element.css('font-size');
91 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
91 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
92
92
93 return (this.element.height() > lines * lineHeight);
93 return (this.element.height() > lines * lineHeight);
94 };
94 };
95
95
96
96
97 OutputArea.prototype.bind_events = function () {
97 OutputArea.prototype.bind_events = function () {
98 var that = this;
98 var that = this;
99 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
99 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
100 this.prompt_overlay.click(function () { that.toggle_scroll(); });
100 this.prompt_overlay.click(function () { that.toggle_scroll(); });
101
101
102 this.element.resize(function () {
102 this.element.resize(function () {
103 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
103 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
104 if ( IPython.utils.browser[0] === "Firefox" ) {
104 if ( IPython.utils.browser[0] === "Firefox" ) {
105 return;
105 return;
106 }
106 }
107 // maybe scroll output,
107 // maybe scroll output,
108 // if it's grown large enough and hasn't already been scrolled.
108 // if it's grown large enough and hasn't already been scrolled.
109 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
109 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
110 that.scroll_area();
110 that.scroll_area();
111 }
111 }
112 });
112 });
113 this.collapse_button.click(function () {
113 this.collapse_button.click(function () {
114 that.expand();
114 that.expand();
115 });
115 });
116 };
116 };
117
117
118
118
119 OutputArea.prototype.collapse = function () {
119 OutputArea.prototype.collapse = function () {
120 if (!this.collapsed) {
120 if (!this.collapsed) {
121 this.element.hide();
121 this.element.hide();
122 this.prompt_overlay.hide();
122 this.prompt_overlay.hide();
123 if (this.element.html()){
123 if (this.element.html()){
124 this.collapse_button.show();
124 this.collapse_button.show();
125 }
125 }
126 this.collapsed = true;
126 this.collapsed = true;
127 }
127 }
128 };
128 };
129
129
130
130
131 OutputArea.prototype.expand = function () {
131 OutputArea.prototype.expand = function () {
132 if (this.collapsed) {
132 if (this.collapsed) {
133 this.collapse_button.hide();
133 this.collapse_button.hide();
134 this.element.show();
134 this.element.show();
135 this.prompt_overlay.show();
135 this.prompt_overlay.show();
136 this.collapsed = false;
136 this.collapsed = false;
137 }
137 }
138 };
138 };
139
139
140
140
141 OutputArea.prototype.toggle_output = function () {
141 OutputArea.prototype.toggle_output = function () {
142 if (this.collapsed) {
142 if (this.collapsed) {
143 this.expand();
143 this.expand();
144 } else {
144 } else {
145 this.collapse();
145 this.collapse();
146 }
146 }
147 };
147 };
148
148
149
149
150 OutputArea.prototype.scroll_area = function () {
150 OutputArea.prototype.scroll_area = function () {
151 this.element.addClass('output_scroll');
151 this.element.addClass('output_scroll');
152 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
152 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
153 this.scrolled = true;
153 this.scrolled = true;
154 };
154 };
155
155
156
156
157 OutputArea.prototype.unscroll_area = function () {
157 OutputArea.prototype.unscroll_area = function () {
158 this.element.removeClass('output_scroll');
158 this.element.removeClass('output_scroll');
159 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
159 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
160 this.scrolled = false;
160 this.scrolled = false;
161 };
161 };
162
162
163 /**
163 /**
164 *
164 *
165 * Scroll OutputArea if height supperior than a threshold (in lines).
165 * Scroll OutputArea if height supperior than a threshold (in lines).
166 *
166 *
167 * Threshold is a maximum number of lines. If unspecified, defaults to
167 * Threshold is a maximum number of lines. If unspecified, defaults to
168 * OutputArea.minimum_scroll_threshold.
168 * OutputArea.minimum_scroll_threshold.
169 *
169 *
170 * Negative threshold will prevent the OutputArea from ever scrolling.
170 * Negative threshold will prevent the OutputArea from ever scrolling.
171 *
171 *
172 * @method scroll_if_long
172 * @method scroll_if_long
173 *
173 *
174 * @param [lines=20]{Number} Default to 20 if not set,
174 * @param [lines=20]{Number} Default to 20 if not set,
175 * behavior undefined for value of `0`.
175 * behavior undefined for value of `0`.
176 *
176 *
177 **/
177 **/
178 OutputArea.prototype.scroll_if_long = function (lines) {
178 OutputArea.prototype.scroll_if_long = function (lines) {
179 var n = lines | OutputArea.minimum_scroll_threshold;
179 var n = lines | OutputArea.minimum_scroll_threshold;
180 if(n <= 0){
180 if(n <= 0){
181 return
181 return
182 }
182 }
183
183
184 if (this._should_scroll(n)) {
184 if (this._should_scroll(n)) {
185 // only allow scrolling long-enough output
185 // only allow scrolling long-enough output
186 this.scroll_area();
186 this.scroll_area();
187 }
187 }
188 };
188 };
189
189
190
190
191 OutputArea.prototype.toggle_scroll = function () {
191 OutputArea.prototype.toggle_scroll = function () {
192 if (this.scrolled) {
192 if (this.scrolled) {
193 this.unscroll_area();
193 this.unscroll_area();
194 } else {
194 } else {
195 // only allow scrolling long-enough output
195 // only allow scrolling long-enough output
196 this.scroll_if_long();
196 this.scroll_if_long();
197 }
197 }
198 };
198 };
199
199
200
200
201 // typeset with MathJax if MathJax is available
201 // typeset with MathJax if MathJax is available
202 OutputArea.prototype.typeset = function () {
202 OutputArea.prototype.typeset = function () {
203 if (window.MathJax){
203 if (window.MathJax){
204 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
204 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
205 }
205 }
206 };
206 };
207
207
208
208
209 OutputArea.prototype.handle_output = function (msg) {
209 OutputArea.prototype.handle_output = function (msg) {
210 var json = {};
210 var json = {};
211 var msg_type = json.output_type = msg.header.msg_type;
211 var msg_type = json.output_type = msg.header.msg_type;
212 var content = msg.content;
212 var content = msg.content;
213 if (msg_type === "stream") {
213 if (msg_type === "stream") {
214 json.text = content.data;
214 json.text = content.data;
215 json.stream = content.name;
215 json.stream = content.name;
216 } else if (msg_type === "display_data") {
216 } else if (msg_type === "display_data") {
217 json = content.data;
217 json = content.data;
218 json.output_type = msg_type;
218 json.output_type = msg_type;
219 json.metadata = content.metadata;
219 json.metadata = content.metadata;
220 } else if (msg_type === "execute_result") {
220 } else if (msg_type === "execute_result") {
221 json = content.data;
221 json = content.data;
222 // pyout message has been renamed to execute_result,
222 // pyout message has been renamed to execute_result,
223 // but the nbformat has not been updated,
223 // but the nbformat has not been updated,
224 // so transform back to pyout for json.
224 // so transform back to pyout for json.
225 json.output_type = "pyout";
225 json.output_type = "pyout";
226 json.metadata = content.metadata;
226 json.metadata = content.metadata;
227 json.prompt_number = content.execution_count;
227 json.prompt_number = content.execution_count;
228 } else if (msg_type === "error") {
228 } else if (msg_type === "error") {
229 // pyerr message has been renamed to error,
229 // pyerr message has been renamed to error,
230 // but the nbformat has not been updated,
230 // but the nbformat has not been updated,
231 // so transform back to pyerr for json.
231 // so transform back to pyerr for json.
232 json.output_type = "pyerr";
232 json.output_type = "pyerr";
233 json = this.convert_mime_types(json, content.data);
233 json = this.convert_mime_types(json, content.data);
234 json.metadata = this.convert_mime_types({}, content.metadata);
234 json.metadata = this.convert_mime_types({}, content.metadata);
235 }
235 }
236 this.append_output(json);
236 this.append_output(json);
237 };
237 };
238
238
239
239
240 OutputArea.prototype.rename_keys = function (data, key_map) {
240 OutputArea.prototype.rename_keys = function (data, key_map) {
241 var remapped = {};
241 var remapped = {};
242 for (var key in data) {
242 for (var key in data) {
243 var new_key = key_map[key] || key;
243 var new_key = key_map[key] || key;
244 remapped[new_key] = data[key];
244 remapped[new_key] = data[key];
245 }
245 }
246 return remapped;
246 return remapped;
247 };
247 };
248
248
249
249
250 OutputArea.output_types = [
250 OutputArea.output_types = [
251 'application/javascript',
251 'application/javascript',
252 'text/html',
252 'text/html',
253 'text/markdown',
253 'text/markdown',
254 'text/latex',
254 'text/latex',
255 'image/svg+xml',
255 'image/svg+xml',
256 'image/png',
256 'image/png',
257 'image/jpeg',
257 'image/jpeg',
258 'application/pdf',
258 'application/pdf',
259 'text/plain'
259 'text/plain'
260 ];
260 ];
261
261
262 OutputArea.prototype.validate_output = function (json) {
262 OutputArea.prototype.validate_output = function (json) {
263 // scrub invalid outputs
263 // scrub invalid outputs
264 // TODO: right now everything is a string, but JSON really shouldn't be.
264 // TODO: right now everything is a string, but JSON really shouldn't be.
265 // nbformat 4 will fix that.
265 // nbformat 4 will fix that.
266 $.map(OutputArea.output_types, function(key){
266 $.map(OutputArea.output_types, function(key){
267 if (json[key] !== undefined && typeof json[key] !== 'string') {
267 if (json[key] !== undefined && typeof json[key] !== 'string') {
268 console.log("Invalid type for " + key, json[key]);
268 console.log("Invalid type for " + key, json[key]);
269 delete json[key];
269 delete json[key];
270 }
270 }
271 });
271 });
272 return json;
272 return json;
273 };
273 };
274
274
275 OutputArea.prototype.append_output = function (json) {
275 OutputArea.prototype.append_output = function (json) {
276 this.expand();
276 this.expand();
277
277
278 // validate output data types
278 // validate output data types
279 json = this.validate_output(json);
279 json = this.validate_output(json);
280
280
281 // Clear the output if clear is queued.
281 // Clear the output if clear is queued.
282 var needs_height_reset = false;
282 var needs_height_reset = false;
283 if (this.clear_queued) {
283 if (this.clear_queued) {
284 this.clear_output(false);
284 this.clear_output(false);
285 needs_height_reset = true;
285 needs_height_reset = true;
286 }
286 }
287
287
288 if (json.output_type === 'pyout') {
288 if (json.output_type === 'pyout') {
289 this.append_execute_result(json);
289 this.append_execute_result(json);
290 } else if (json.output_type === 'pyerr') {
290 } else if (json.output_type === 'pyerr') {
291 this.append_error(json);
291 this.append_error(json);
292 } else if (json.output_type === 'stream') {
292 } else if (json.output_type === 'stream') {
293 this.append_stream(json);
293 this.append_stream(json);
294 }
294 }
295
295
296 // We must release the animation fixed height in a callback since Gecko
296 // We must release the animation fixed height in a callback since Gecko
297 // (FireFox) doesn't render the image immediately as the data is
297 // (FireFox) doesn't render the image immediately as the data is
298 // available.
298 // available.
299 var that = this;
299 var that = this;
300 var handle_appended = function ($el) {
300 var handle_appended = function ($el) {
301 // Only reset the height to automatic if the height is currently
301 // Only reset the height to automatic if the height is currently
302 // fixed (done by wait=True flag on clear_output).
302 // fixed (done by wait=True flag on clear_output).
303 if (needs_height_reset) {
303 if (needs_height_reset) {
304 that.element.height('');
304 that.element.height('');
305 }
305 }
306 that.element.trigger('resize');
306 that.element.trigger('resize');
307 };
307 };
308 if (json.output_type === 'display_data') {
308 if (json.output_type === 'display_data') {
309 this.append_display_data(json, handle_appended);
309 this.append_display_data(json, handle_appended);
310 } else {
310 } else {
311 handle_appended();
311 handle_appended();
312 }
312 }
313
313
314 this.outputs.push(json);
314 this.outputs.push(json);
315 };
315 };
316
316
317
317
318 OutputArea.prototype.create_output_area = function () {
318 OutputArea.prototype.create_output_area = function () {
319 var oa = $("<div/>").addClass("output_area");
319 var oa = $("<div/>").addClass("output_area");
320 if (this.prompt_area) {
320 if (this.prompt_area) {
321 oa.append($('<div/>').addClass('prompt'));
321 oa.append($('<div/>').addClass('prompt'));
322 }
322 }
323 return oa;
323 return oa;
324 };
324 };
325
325
326
326
327 function _get_metadata_key(metadata, key, mime) {
327 function _get_metadata_key(metadata, key, mime) {
328 var mime_md = metadata[mime];
328 var mime_md = metadata[mime];
329 // mime-specific higher priority
329 // mime-specific higher priority
330 if (mime_md && mime_md[key] !== undefined) {
330 if (mime_md && mime_md[key] !== undefined) {
331 return mime_md[key];
331 return mime_md[key];
332 }
332 }
333 // fallback on global
333 // fallback on global
334 return metadata[key];
334 return metadata[key];
335 }
335 }
336
336
337 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
337 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
338 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
338 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
339 if (_get_metadata_key(md, 'isolated', mime)) {
339 if (_get_metadata_key(md, 'isolated', mime)) {
340 // Create an iframe to isolate the subarea from the rest of the
340 // Create an iframe to isolate the subarea from the rest of the
341 // document
341 // document
342 var iframe = $('<iframe/>').addClass('box-flex1');
342 var iframe = $('<iframe/>').addClass('box-flex1');
343 iframe.css({'height':1, 'width':'100%', 'display':'block'});
343 iframe.css({'height':1, 'width':'100%', 'display':'block'});
344 iframe.attr('frameborder', 0);
344 iframe.attr('frameborder', 0);
345 iframe.attr('scrolling', 'auto');
345 iframe.attr('scrolling', 'auto');
346
346
347 // Once the iframe is loaded, the subarea is dynamically inserted
347 // Once the iframe is loaded, the subarea is dynamically inserted
348 iframe.on('load', function() {
348 iframe.on('load', function() {
349 // Workaround needed by Firefox, to properly render svg inside
349 // Workaround needed by Firefox, to properly render svg inside
350 // iframes, see http://stackoverflow.com/questions/10177190/
350 // iframes, see http://stackoverflow.com/questions/10177190/
351 // svg-dynamically-added-to-iframe-does-not-render-correctly
351 // svg-dynamically-added-to-iframe-does-not-render-correctly
352 this.contentDocument.open();
352 this.contentDocument.open();
353
353
354 // Insert the subarea into the iframe
354 // Insert the subarea into the iframe
355 // We must directly write the html. When using Jquery's append
355 // We must directly write the html. When using Jquery's append
356 // method, javascript is evaluated in the parent document and
356 // method, javascript is evaluated in the parent document and
357 // not in the iframe document. At this point, subarea doesn't
357 // not in the iframe document. At this point, subarea doesn't
358 // contain any user content.
358 // contain any user content.
359 this.contentDocument.write(subarea.html());
359 this.contentDocument.write(subarea.html());
360
360
361 this.contentDocument.close();
361 this.contentDocument.close();
362
362
363 var body = this.contentDocument.body;
363 var body = this.contentDocument.body;
364 // Adjust the iframe height automatically
364 // Adjust the iframe height automatically
365 iframe.height(body.scrollHeight + 'px');
365 iframe.height(body.scrollHeight + 'px');
366 });
366 });
367
367
368 // Elements should be appended to the inner subarea and not to the
368 // Elements should be appended to the inner subarea and not to the
369 // iframe
369 // iframe
370 iframe.append = function(that) {
370 iframe.append = function(that) {
371 subarea.append(that);
371 subarea.append(that);
372 };
372 };
373
373
374 return iframe;
374 return iframe;
375 } else {
375 } else {
376 return subarea;
376 return subarea;
377 }
377 }
378 }
378 }
379
379
380
380
381 OutputArea.prototype._append_javascript_error = function (err, element) {
381 OutputArea.prototype._append_javascript_error = function (err, element) {
382 // display a message when a javascript error occurs in display output
382 // display a message when a javascript error occurs in display output
383 var msg = "Javascript error adding output!"
383 var msg = "Javascript error adding output!"
384 if ( element === undefined ) return;
384 if ( element === undefined ) return;
385 element
385 element
386 .append($('<div/>').text(msg).addClass('js-error'))
386 .append($('<div/>').text(msg).addClass('js-error'))
387 .append($('<div/>').text(err.toString()).addClass('js-error'))
387 .append($('<div/>').text(err.toString()).addClass('js-error'))
388 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
388 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
389 };
389 };
390
390
391 OutputArea.prototype._safe_append = function (toinsert) {
391 OutputArea.prototype._safe_append = function (toinsert) {
392 // safely append an item to the document
392 // safely append an item to the document
393 // this is an object created by user code,
393 // this is an object created by user code,
394 // and may have errors, which should not be raised
394 // and may have errors, which should not be raised
395 // under any circumstances.
395 // under any circumstances.
396 try {
396 try {
397 this.element.append(toinsert);
397 this.element.append(toinsert);
398 } catch(err) {
398 } catch(err) {
399 console.log(err);
399 console.log(err);
400 // Create an actual output_area and output_subarea, which creates
400 // Create an actual output_area and output_subarea, which creates
401 // the prompt area and the proper indentation.
401 // the prompt area and the proper indentation.
402 var toinsert = this.create_output_area();
402 var toinsert = this.create_output_area();
403 var subarea = $('<div/>').addClass('output_subarea');
403 var subarea = $('<div/>').addClass('output_subarea');
404 toinsert.append(subarea);
404 toinsert.append(subarea);
405 this._append_javascript_error(err, subarea);
405 this._append_javascript_error(err, subarea);
406 this.element.append(toinsert);
406 this.element.append(toinsert);
407 }
407 }
408 };
408 };
409
409
410
410
411 OutputArea.prototype.append_execute_result = function (json) {
411 OutputArea.prototype.append_execute_result = function (json) {
412 var n = json.prompt_number || ' ';
412 var n = json.prompt_number || ' ';
413 var toinsert = this.create_output_area();
413 var toinsert = this.create_output_area();
414 if (this.prompt_area) {
414 if (this.prompt_area) {
415 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
415 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
416 }
416 }
417 var inserted = this.append_mime_type(json, toinsert);
417 var inserted = this.append_mime_type(json, toinsert);
418 if (inserted) {
418 if (inserted) {
419 inserted.addClass('output_pyout');
419 inserted.addClass('output_pyout');
420 }
420 }
421 this._safe_append(toinsert);
421 this._safe_append(toinsert);
422 // If we just output latex, typeset it.
422 // If we just output latex, typeset it.
423 if ((json['text/latex'] !== undefined) ||
423 if ((json['text/latex'] !== undefined) ||
424 (json['text/html'] !== undefined) ||
424 (json['text/html'] !== undefined) ||
425 (json['text/markdown'] !== undefined)) {
425 (json['text/markdown'] !== undefined)) {
426 this.typeset();
426 this.typeset();
427 }
427 }
428 };
428 };
429
429
430
430
431 OutputArea.prototype.append_error = function (json) {
431 OutputArea.prototype.append_error = function (json) {
432 var tb = json.traceback;
432 var tb = json.traceback;
433 if (tb !== undefined && tb.length > 0) {
433 if (tb !== undefined && tb.length > 0) {
434 var s = '';
434 var s = '';
435 var len = tb.length;
435 var len = tb.length;
436 for (var i=0; i<len; i++) {
436 for (var i=0; i<len; i++) {
437 s = s + tb[i] + '\n';
437 s = s + tb[i] + '\n';
438 }
438 }
439 s = s + '\n';
439 s = s + '\n';
440 var toinsert = this.create_output_area();
440 var toinsert = this.create_output_area();
441 var append_text = OutputArea.append_map['text/plain'];
441 var append_text = OutputArea.append_map['text/plain'];
442 if (append_text) {
442 if (append_text) {
443 append_text.apply(this, [s, {}, toinsert]).addClass('output_pyerr');
443 append_text.apply(this, [s, {}, toinsert]).addClass('output_pyerr');
444 }
444 }
445 this._safe_append(toinsert);
445 this._safe_append(toinsert);
446 }
446 }
447 };
447 };
448
448
449
449
450 OutputArea.prototype.append_stream = function (json) {
450 OutputArea.prototype.append_stream = function (json) {
451 // temporary fix: if stream undefined (json file written prior to this patch),
451 // temporary fix: if stream undefined (json file written prior to this patch),
452 // default to most likely stdout:
452 // default to most likely stdout:
453 if (json.stream === undefined){
453 if (json.stream === undefined){
454 json.stream = 'stdout';
454 json.stream = 'stdout';
455 }
455 }
456 var text = json.text;
456 var text = json.text;
457 var subclass = "output_"+json.stream;
457 var subclass = "output_"+json.stream;
458 if (this.outputs.length > 0){
458 if (this.outputs.length > 0){
459 // have at least one output to consider
459 // have at least one output to consider
460 var last = this.outputs[this.outputs.length-1];
460 var last = this.outputs[this.outputs.length-1];
461 if (last.output_type == 'stream' && json.stream == last.stream){
461 if (last.output_type == 'stream' && json.stream == last.stream){
462 // latest output was in the same stream,
462 // latest output was in the same stream,
463 // so append directly into its pre tag
463 // so append directly into its pre tag
464 // escape ANSI & HTML specials:
464 // escape ANSI & HTML specials:
465 var pre = this.element.find('div.'+subclass).last().find('pre');
465 var pre = this.element.find('div.'+subclass).last().find('pre');
466 var html = utils.fixCarriageReturn(
466 var html = utils.fixCarriageReturn(
467 pre.html() + utils.fixConsole(text));
467 pre.html() + utils.fixConsole(text));
468 // The only user content injected with this HTML call is
468 // The only user content injected with this HTML call is
469 // escaped by the fixConsole() method.
469 // escaped by the fixConsole() method.
470 pre.html(html);
470 pre.html(html);
471 return;
471 return;
472 }
472 }
473 }
473 }
474
474
475 if (!text.replace("\r", "")) {
475 if (!text.replace("\r", "")) {
476 // text is nothing (empty string, \r, etc.)
476 // text is nothing (empty string, \r, etc.)
477 // so don't append any elements, which might add undesirable space
477 // so don't append any elements, which might add undesirable space
478 return;
478 return;
479 }
479 }
480
480
481 // If we got here, attach a new div
481 // If we got here, attach a new div
482 var toinsert = this.create_output_area();
482 var toinsert = this.create_output_area();
483 var append_text = OutputArea.append_map['text/plain'];
483 var append_text = OutputArea.append_map['text/plain'];
484 if (append_text) {
484 if (append_text) {
485 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
485 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
486 }
486 }
487 this._safe_append(toinsert);
487 this._safe_append(toinsert);
488 };
488 };
489
489
490
490
491 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
491 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
492 var toinsert = this.create_output_area();
492 var toinsert = this.create_output_area();
493 if (this.append_mime_type(json, toinsert, handle_inserted)) {
493 if (this.append_mime_type(json, toinsert, handle_inserted)) {
494 this._safe_append(toinsert);
494 this._safe_append(toinsert);
495 // If we just output latex, typeset it.
495 // If we just output latex, typeset it.
496 if ((json['text/latex'] !== undefined) ||
496 if ((json['text/latex'] !== undefined) ||
497 (json['text/html'] !== undefined) ||
497 (json['text/html'] !== undefined) ||
498 (json['text/markdown'] !== undefined)) {
498 (json['text/markdown'] !== undefined)) {
499 this.typeset();
499 this.typeset();
500 }
500 }
501 }
501 }
502 };
502 };
503
503
504
504
505 OutputArea.safe_outputs = {
505 OutputArea.safe_outputs = {
506 'text/plain' : true,
506 'text/plain' : true,
507 'text/latex' : true,
507 'text/latex' : true,
508 'image/png' : true,
508 'image/png' : true,
509 'image/jpeg' : true
509 'image/jpeg' : true
510 };
510 };
511
511
512 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
512 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
513 for (var i=0; i < OutputArea.display_order.length; i++) {
513 for (var i=0; i < OutputArea.display_order.length; i++) {
514 var type = OutputArea.display_order[i];
514 var type = OutputArea.display_order[i];
515 var append = OutputArea.append_map[type];
515 var append = OutputArea.append_map[type];
516 if ((json[type] !== undefined) && append) {
516 if ((json[type] !== undefined) && append) {
517 var value = json[type];
517 var value = json[type];
518 if (!this.trusted && !OutputArea.safe_outputs[type]) {
518 if (!this.trusted && !OutputArea.safe_outputs[type]) {
519 // not trusted, sanitize HTML
519 // not trusted, sanitize HTML
520 if (type==='text/html' || type==='text/svg') {
520 if (type==='text/html' || type==='text/svg') {
521 value = IPython.security.sanitize_html(value);
521 value = IPython.security.sanitize_html(value);
522 } else {
522 } else {
523 // don't display if we don't know how to sanitize it
523 // don't display if we don't know how to sanitize it
524 console.log("Ignoring untrusted " + type + " output.");
524 console.log("Ignoring untrusted " + type + " output.");
525 continue;
525 continue;
526 }
526 }
527 }
527 }
528 var md = json.metadata || {};
528 var md = json.metadata || {};
529 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
529 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
530 // Since only the png and jpeg mime types call the inserted
530 // Since only the png and jpeg mime types call the inserted
531 // callback, if the mime type is something other we must call the
531 // callback, if the mime type is something other we must call the
532 // inserted callback only when the element is actually inserted
532 // inserted callback only when the element is actually inserted
533 // into the DOM. Use a timeout of 0 to do this.
533 // into the DOM. Use a timeout of 0 to do this.
534 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
534 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
535 setTimeout(handle_inserted, 0);
535 setTimeout(handle_inserted, 0);
536 }
536 }
537 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
537 $([IPython.events]).trigger('output_appended.OutputArea', [type, value, md, toinsert]);
538 return toinsert;
538 return toinsert;
539 }
539 }
540 }
540 }
541 return null;
541 return null;
542 };
542 };
543
543
544
544
545 var append_html = function (html, md, element) {
545 var append_html = function (html, md, element) {
546 var type = 'text/html';
546 var type = 'text/html';
547 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
547 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
548 IPython.keyboard_manager.register_events(toinsert);
548 IPython.keyboard_manager.register_events(toinsert);
549 toinsert.append(html);
549 toinsert.append(html);
550 element.append(toinsert);
550 element.append(toinsert);
551 return toinsert;
551 return toinsert;
552 };
552 };
553
553
554
554
555 var append_markdown = function(markdown, md, element) {
555 var append_markdown = function(markdown, md, element) {
556 var type = 'text/markdown';
556 var type = 'text/markdown';
557 var toinsert = this.create_output_subarea(md, "output_markdown", type);
557 var toinsert = this.create_output_subarea(md, "output_markdown", type);
558 var text_and_math = IPython.mathjaxutils.remove_math(markdown);
558 var text_and_math = IPython.mathjaxutils.remove_math(markdown);
559 var text = text_and_math[0];
559 var text = text_and_math[0];
560 var math = text_and_math[1];
560 var math = text_and_math[1];
561 var html = marked.parser(marked.lexer(text));
561 var html = marked.parser(marked.lexer(text));
562 html = IPython.mathjaxutils.replace_math(html, math);
562 html = IPython.mathjaxutils.replace_math(html, math);
563 toinsert.append(html);
563 toinsert.append(html);
564 element.append(toinsert);
564 element.append(toinsert);
565 return toinsert;
565 return toinsert;
566 };
566 };
567
567
568
568
569 var append_javascript = function (js, md, element) {
569 var append_javascript = function (js, md, element) {
570 // We just eval the JS code, element appears in the local scope.
570 // We just eval the JS code, element appears in the local scope.
571 var type = 'application/javascript';
571 var type = 'application/javascript';
572 var toinsert = this.create_output_subarea(md, "output_javascript", type);
572 var toinsert = this.create_output_subarea(md, "output_javascript", type);
573 IPython.keyboard_manager.register_events(toinsert);
573 IPython.keyboard_manager.register_events(toinsert);
574 element.append(toinsert);
574 element.append(toinsert);
575 // FIXME TODO : remove `container element for 3.0`
575 // FIXME TODO : remove `container element for 3.0`
576 //backward compat, js should be eval'ed in a context where `container` is defined.
576 //backward compat, js should be eval'ed in a context where `container` is defined.
577 var container = element;
577 var container = element;
578 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
578 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
579 // end backward compat
579 // end backward compat
580
580
581 // Fix for ipython/issues/5293, make sure `element` is the area which
581 // Fix for ipython/issues/5293, make sure `element` is the area which
582 // output can be inserted into at the time of JS execution.
582 // output can be inserted into at the time of JS execution.
583 element = toinsert;
583 element = toinsert;
584 try {
584 try {
585 eval(js);
585 eval(js);
586 } catch(err) {
586 } catch(err) {
587 console.log(err);
587 console.log(err);
588 this._append_javascript_error(err, toinsert);
588 this._append_javascript_error(err, toinsert);
589 }
589 }
590 return toinsert;
590 return toinsert;
591 };
591 };
592
592
593
593
594 var append_text = function (data, md, element) {
594 var append_text = function (data, md, element) {
595 var type = 'text/plain';
595 var type = 'text/plain';
596 var toinsert = this.create_output_subarea(md, "output_text", type);
596 var toinsert = this.create_output_subarea(md, "output_text", type);
597 // escape ANSI & HTML specials in plaintext:
597 // escape ANSI & HTML specials in plaintext:
598 data = utils.fixConsole(data);
598 data = utils.fixConsole(data);
599 data = utils.fixCarriageReturn(data);
599 data = utils.fixCarriageReturn(data);
600 data = utils.autoLinkUrls(data);
600 data = utils.autoLinkUrls(data);
601 // The only user content injected with this HTML call is
601 // The only user content injected with this HTML call is
602 // escaped by the fixConsole() method.
602 // escaped by the fixConsole() method.
603 toinsert.append($("<pre/>").html(data));
603 toinsert.append($("<pre/>").html(data));
604 element.append(toinsert);
604 element.append(toinsert);
605 return toinsert;
605 return toinsert;
606 };
606 };
607
607
608
608
609 var append_svg = function (svg_html, md, element) {
609 var append_svg = function (svg_html, md, element) {
610 var type = 'image/svg+xml';
610 var type = 'image/svg+xml';
611 var toinsert = this.create_output_subarea(md, "output_svg", type);
611 var toinsert = this.create_output_subarea(md, "output_svg", type);
612
612
613 // Get the svg element from within the HTML.
613 // Get the svg element from within the HTML.
614 var svg = $('<div />').html(svg_html).find('svg');
614 var svg = $('<div />').html(svg_html).find('svg');
615 var svg_area = $('<div />');
615 var svg_area = $('<div />');
616 var width = svg.attr('width');
616 var width = svg.attr('width');
617 var height = svg.attr('height');
617 var height = svg.attr('height');
618 svg
618 svg
619 .width('100%')
619 .width('100%')
620 .height('100%');
620 .height('100%');
621 svg_area
621 svg_area
622 .width(width)
622 .width(width)
623 .height(height);
623 .height(height);
624
624
625 // The jQuery resize handlers don't seem to work on the svg element.
625 // The jQuery resize handlers don't seem to work on the svg element.
626 // When the svg renders completely, measure it's size and set the parent
626 // When the svg renders completely, measure it's size and set the parent
627 // div to that size. Then set the svg to 100% the size of the parent
627 // div to that size. Then set the svg to 100% the size of the parent
628 // div and make the parent div resizable.
628 // div and make the parent div resizable.
629 this._dblclick_to_reset_size(svg_area, true, false);
629 this._dblclick_to_reset_size(svg_area, true, false);
630
630
631 svg_area.append(svg);
631 svg_area.append(svg);
632 toinsert.append(svg_area);
632 toinsert.append(svg_area);
633 element.append(toinsert);
633 element.append(toinsert);
634
634
635 return toinsert;
635 return toinsert;
636 };
636 };
637
637
638 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
638 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
639 // Add a resize handler to an element
639 // Add a resize handler to an element
640 //
640 //
641 // img: jQuery element
641 // img: jQuery element
642 // immediately: bool=False
642 // immediately: bool=False
643 // Wait for the element to load before creating the handle.
643 // Wait for the element to load before creating the handle.
644 // resize_parent: bool=True
644 // resize_parent: bool=True
645 // Should the parent of the element be resized when the element is
645 // Should the parent of the element be resized when the element is
646 // reset (by double click).
646 // reset (by double click).
647 var callback = function (){
647 var callback = function (){
648 var h0 = img.height();
648 var h0 = img.height();
649 var w0 = img.width();
649 var w0 = img.width();
650 if (!(h0 && w0)) {
650 if (!(h0 && w0)) {
651 // zero size, don't make it resizable
651 // zero size, don't make it resizable
652 return;
652 return;
653 }
653 }
654 img.resizable({
654 img.resizable({
655 aspectRatio: true,
655 aspectRatio: true,
656 autoHide: true
656 autoHide: true
657 });
657 });
658 img.dblclick(function () {
658 img.dblclick(function () {
659 // resize wrapper & image together for some reason:
659 // resize wrapper & image together for some reason:
660 img.height(h0);
660 img.height(h0);
661 img.width(w0);
661 img.width(w0);
662 if (resize_parent === undefined || resize_parent) {
662 if (resize_parent === undefined || resize_parent) {
663 img.parent().height(h0);
663 img.parent().height(h0);
664 img.parent().width(w0);
664 img.parent().width(w0);
665 }
665 }
666 });
666 });
667 };
667 };
668
668
669 if (immediately) {
669 if (immediately) {
670 callback();
670 callback();
671 } else {
671 } else {
672 img.on("load", callback);
672 img.on("load", callback);
673 }
673 }
674 };
674 };
675
675
676 var set_width_height = function (img, md, mime) {
676 var set_width_height = function (img, md, mime) {
677 // set width and height of an img element from metadata
677 // set width and height of an img element from metadata
678 var height = _get_metadata_key(md, 'height', mime);
678 var height = _get_metadata_key(md, 'height', mime);
679 if (height !== undefined) img.attr('height', height);
679 if (height !== undefined) img.attr('height', height);
680 var width = _get_metadata_key(md, 'width', mime);
680 var width = _get_metadata_key(md, 'width', mime);
681 if (width !== undefined) img.attr('width', width);
681 if (width !== undefined) img.attr('width', width);
682 };
682 };
683
683
684 var append_png = function (png, md, element, handle_inserted) {
684 var append_png = function (png, md, element, handle_inserted) {
685 var type = 'image/png';
685 var type = 'image/png';
686 var toinsert = this.create_output_subarea(md, "output_png", type);
686 var toinsert = this.create_output_subarea(md, "output_png", type);
687 var img = $("<img/>");
687 var img = $("<img/>");
688 if (handle_inserted !== undefined) {
688 if (handle_inserted !== undefined) {
689 img.on('load', function(){
689 img.on('load', function(){
690 handle_inserted(img);
690 handle_inserted(img);
691 });
691 });
692 }
692 }
693 img[0].src = 'data:image/png;base64,'+ png;
693 img[0].src = 'data:image/png;base64,'+ png;
694 set_width_height(img, md, 'image/png');
694 set_width_height(img, md, 'image/png');
695 this._dblclick_to_reset_size(img);
695 this._dblclick_to_reset_size(img);
696 toinsert.append(img);
696 toinsert.append(img);
697 element.append(toinsert);
697 element.append(toinsert);
698 return toinsert;
698 return toinsert;
699 };
699 };
700
700
701
701
702 var append_jpeg = function (jpeg, md, element, handle_inserted) {
702 var append_jpeg = function (jpeg, md, element, handle_inserted) {
703 var type = 'image/jpeg';
703 var type = 'image/jpeg';
704 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
704 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
705 var img = $("<img/>");
705 var img = $("<img/>");
706 if (handle_inserted !== undefined) {
706 if (handle_inserted !== undefined) {
707 img.on('load', function(){
707 img.on('load', function(){
708 handle_inserted(img);
708 handle_inserted(img);
709 });
709 });
710 }
710 }
711 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
711 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
712 set_width_height(img, md, 'image/jpeg');
712 set_width_height(img, md, 'image/jpeg');
713 this._dblclick_to_reset_size(img);
713 this._dblclick_to_reset_size(img);
714 toinsert.append(img);
714 toinsert.append(img);
715 element.append(toinsert);
715 element.append(toinsert);
716 return toinsert;
716 return toinsert;
717 };
717 };
718
718
719
719
720 var append_pdf = function (pdf, md, element) {
720 var append_pdf = function (pdf, md, element) {
721 var type = 'application/pdf';
721 var type = 'application/pdf';
722 var toinsert = this.create_output_subarea(md, "output_pdf", type);
722 var toinsert = this.create_output_subarea(md, "output_pdf", type);
723 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
723 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
724 a.attr('target', '_blank');
724 a.attr('target', '_blank');
725 a.text('View PDF')
725 a.text('View PDF')
726 toinsert.append(a);
726 toinsert.append(a);
727 element.append(toinsert);
727 element.append(toinsert);
728 return toinsert;
728 return toinsert;
729 }
729 }
730
730
731 var append_latex = function (latex, md, element) {
731 var append_latex = function (latex, md, element) {
732 // This method cannot do the typesetting because the latex first has to
732 // This method cannot do the typesetting because the latex first has to
733 // be on the page.
733 // be on the page.
734 var type = 'text/latex';
734 var type = 'text/latex';
735 var toinsert = this.create_output_subarea(md, "output_latex", type);
735 var toinsert = this.create_output_subarea(md, "output_latex", type);
736 toinsert.append(latex);
736 toinsert.append(latex);
737 element.append(toinsert);
737 element.append(toinsert);
738 return toinsert;
738 return toinsert;
739 };
739 };
740
740
741
741
742 OutputArea.prototype.append_raw_input = function (msg) {
742 OutputArea.prototype.append_raw_input = function (msg) {
743 var that = this;
743 var that = this;
744 this.expand();
744 this.expand();
745 var content = msg.content;
745 var content = msg.content;
746 var area = this.create_output_area();
746 var area = this.create_output_area();
747
747
748 // disable any other raw_inputs, if they are left around
748 // disable any other raw_inputs, if they are left around
749 $("div.output_subarea.raw_input_container").remove();
749 $("div.output_subarea.raw_input_container").remove();
750
750
751 var input_type = content.password ? 'password' : 'text';
752
751 area.append(
753 area.append(
752 $("<div/>")
754 $("<div/>")
753 .addClass("box-flex1 output_subarea raw_input_container")
755 .addClass("box-flex1 output_subarea raw_input_container")
754 .append(
756 .append(
755 $("<span/>")
757 $("<span/>")
756 .addClass("raw_input_prompt")
758 .addClass("raw_input_prompt")
757 .text(content.prompt)
759 .text(content.prompt)
758 )
760 )
759 .append(
761 .append(
760 $("<input/>")
762 $("<input/>")
761 .addClass("raw_input")
763 .addClass("raw_input")
762 .attr('type', 'text')
764 .attr('type', input_type)
763 .attr("size", 47)
765 .attr("size", 47)
764 .keydown(function (event, ui) {
766 .keydown(function (event, ui) {
765 // make sure we submit on enter,
767 // make sure we submit on enter,
766 // and don't re-execute the *cell* on shift-enter
768 // and don't re-execute the *cell* on shift-enter
767 if (event.which === IPython.keyboard.keycodes.enter) {
769 if (event.which === IPython.keyboard.keycodes.enter) {
768 that._submit_raw_input();
770 that._submit_raw_input();
769 return false;
771 return false;
770 }
772 }
771 })
773 })
772 )
774 )
773 );
775 );
774
776
775 this.element.append(area);
777 this.element.append(area);
776 var raw_input = area.find('input.raw_input');
778 var raw_input = area.find('input.raw_input');
777 // Register events that enable/disable the keyboard manager while raw
779 // Register events that enable/disable the keyboard manager while raw
778 // input is focused.
780 // input is focused.
779 IPython.keyboard_manager.register_events(raw_input);
781 IPython.keyboard_manager.register_events(raw_input);
780 // Note, the following line used to read raw_input.focus().focus().
782 // Note, the following line used to read raw_input.focus().focus().
781 // This seemed to be needed otherwise only the cell would be focused.
783 // This seemed to be needed otherwise only the cell would be focused.
782 // But with the modal UI, this seems to work fine with one call to focus().
784 // But with the modal UI, this seems to work fine with one call to focus().
783 raw_input.focus();
785 raw_input.focus();
784 }
786 }
785
787
786 OutputArea.prototype._submit_raw_input = function (evt) {
788 OutputArea.prototype._submit_raw_input = function (evt) {
787 var container = this.element.find("div.raw_input_container");
789 var container = this.element.find("div.raw_input_container");
788 var theprompt = container.find("span.raw_input_prompt");
790 var theprompt = container.find("span.raw_input_prompt");
789 var theinput = container.find("input.raw_input");
791 var theinput = container.find("input.raw_input");
790 var value = theinput.val();
792 var value = theinput.val();
793 var echo = value;
794 // don't echo if it's a password
795 if (theinput.attr('type') == 'password') {
796 echo = '········';
797 }
791 var content = {
798 var content = {
792 output_type : 'stream',
799 output_type : 'stream',
793 name : 'stdout',
800 name : 'stdout',
794 text : theprompt.text() + value + '\n'
801 text : theprompt.text() + echo + '\n'
795 }
802 }
796 // remove form container
803 // remove form container
797 container.parent().remove();
804 container.parent().remove();
798 // replace with plaintext version in stdout
805 // replace with plaintext version in stdout
799 this.append_output(content, false);
806 this.append_output(content, false);
800 $([IPython.events]).trigger('send_input_reply.Kernel', value);
807 $([IPython.events]).trigger('send_input_reply.Kernel', value);
801 }
808 }
802
809
803
810
804 OutputArea.prototype.handle_clear_output = function (msg) {
811 OutputArea.prototype.handle_clear_output = function (msg) {
805 // msg spec v4 had stdout, stderr, display keys
812 // msg spec v4 had stdout, stderr, display keys
806 // v4.1 replaced these with just wait
813 // v4.1 replaced these with just wait
807 // The default behavior is the same (stdout=stderr=display=True, wait=False),
814 // The default behavior is the same (stdout=stderr=display=True, wait=False),
808 // so v4 messages will still be properly handled,
815 // so v4 messages will still be properly handled,
809 // except for the rarely used clearing less than all output.
816 // except for the rarely used clearing less than all output.
810 this.clear_output(msg.content.wait || false);
817 this.clear_output(msg.content.wait || false);
811 };
818 };
812
819
813
820
814 OutputArea.prototype.clear_output = function(wait) {
821 OutputArea.prototype.clear_output = function(wait) {
815 if (wait) {
822 if (wait) {
816
823
817 // If a clear is queued, clear before adding another to the queue.
824 // If a clear is queued, clear before adding another to the queue.
818 if (this.clear_queued) {
825 if (this.clear_queued) {
819 this.clear_output(false);
826 this.clear_output(false);
820 };
827 };
821
828
822 this.clear_queued = true;
829 this.clear_queued = true;
823 } else {
830 } else {
824
831
825 // Fix the output div's height if the clear_output is waiting for
832 // Fix the output div's height if the clear_output is waiting for
826 // new output (it is being used in an animation).
833 // new output (it is being used in an animation).
827 if (this.clear_queued) {
834 if (this.clear_queued) {
828 var height = this.element.height();
835 var height = this.element.height();
829 this.element.height(height);
836 this.element.height(height);
830 this.clear_queued = false;
837 this.clear_queued = false;
831 }
838 }
832
839
833 // Clear all
840 // Clear all
834 // Remove load event handlers from img tags because we don't want
841 // Remove load event handlers from img tags because we don't want
835 // them to fire if the image is never added to the page.
842 // them to fire if the image is never added to the page.
836 this.element.find('img').off('load');
843 this.element.find('img').off('load');
837 this.element.html("");
844 this.element.html("");
838 this.outputs = [];
845 this.outputs = [];
839 this.trusted = true;
846 this.trusted = true;
840 this.unscroll_area();
847 this.unscroll_area();
841 return;
848 return;
842 };
849 };
843 };
850 };
844
851
845
852
846 // JSON serialization
853 // JSON serialization
847
854
848 OutputArea.prototype.fromJSON = function (outputs) {
855 OutputArea.prototype.fromJSON = function (outputs) {
849 var len = outputs.length;
856 var len = outputs.length;
850 var data;
857 var data;
851
858
852 for (var i=0; i<len; i++) {
859 for (var i=0; i<len; i++) {
853 data = outputs[i];
860 data = outputs[i];
854 var msg_type = data.output_type;
861 var msg_type = data.output_type;
855 if (msg_type === "display_data" || msg_type === "pyout") {
862 if (msg_type === "display_data" || msg_type === "pyout") {
856 // convert short keys to mime keys
863 // convert short keys to mime keys
857 // TODO: remove mapping of short keys when we update to nbformat 4
864 // TODO: remove mapping of short keys when we update to nbformat 4
858 data = this.rename_keys(data, OutputArea.mime_map_r);
865 data = this.rename_keys(data, OutputArea.mime_map_r);
859 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
866 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
860 }
867 }
861
868
862 this.append_output(data);
869 this.append_output(data);
863 }
870 }
864 };
871 };
865
872
866
873
867 OutputArea.prototype.toJSON = function () {
874 OutputArea.prototype.toJSON = function () {
868 var outputs = [];
875 var outputs = [];
869 var len = this.outputs.length;
876 var len = this.outputs.length;
870 var data;
877 var data;
871 for (var i=0; i<len; i++) {
878 for (var i=0; i<len; i++) {
872 data = this.outputs[i];
879 data = this.outputs[i];
873 var msg_type = data.output_type;
880 var msg_type = data.output_type;
874 if (msg_type === "display_data" || msg_type === "pyout") {
881 if (msg_type === "display_data" || msg_type === "pyout") {
875 // convert mime keys to short keys
882 // convert mime keys to short keys
876 data = this.rename_keys(data, OutputArea.mime_map);
883 data = this.rename_keys(data, OutputArea.mime_map);
877 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
884 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
878 }
885 }
879 outputs[i] = data;
886 outputs[i] = data;
880 }
887 }
881 return outputs;
888 return outputs;
882 };
889 };
883
890
884 /**
891 /**
885 * Class properties
892 * Class properties
886 **/
893 **/
887
894
888 /**
895 /**
889 * Threshold to trigger autoscroll when the OutputArea is resized,
896 * Threshold to trigger autoscroll when the OutputArea is resized,
890 * typically when new outputs are added.
897 * typically when new outputs are added.
891 *
898 *
892 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
899 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
893 * unless it is < 0, in which case autoscroll will never be triggered
900 * unless it is < 0, in which case autoscroll will never be triggered
894 *
901 *
895 * @property auto_scroll_threshold
902 * @property auto_scroll_threshold
896 * @type Number
903 * @type Number
897 * @default 100
904 * @default 100
898 *
905 *
899 **/
906 **/
900 OutputArea.auto_scroll_threshold = 100;
907 OutputArea.auto_scroll_threshold = 100;
901
908
902 /**
909 /**
903 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
910 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
904 * shorter than this are never scrolled.
911 * shorter than this are never scrolled.
905 *
912 *
906 * @property minimum_scroll_threshold
913 * @property minimum_scroll_threshold
907 * @type Number
914 * @type Number
908 * @default 20
915 * @default 20
909 *
916 *
910 **/
917 **/
911 OutputArea.minimum_scroll_threshold = 20;
918 OutputArea.minimum_scroll_threshold = 20;
912
919
913
920
914
921
915 OutputArea.mime_map = {
922 OutputArea.mime_map = {
916 "text/plain" : "text",
923 "text/plain" : "text",
917 "text/html" : "html",
924 "text/html" : "html",
918 "image/svg+xml" : "svg",
925 "image/svg+xml" : "svg",
919 "image/png" : "png",
926 "image/png" : "png",
920 "image/jpeg" : "jpeg",
927 "image/jpeg" : "jpeg",
921 "text/latex" : "latex",
928 "text/latex" : "latex",
922 "application/json" : "json",
929 "application/json" : "json",
923 "application/javascript" : "javascript",
930 "application/javascript" : "javascript",
924 };
931 };
925
932
926 OutputArea.mime_map_r = {
933 OutputArea.mime_map_r = {
927 "text" : "text/plain",
934 "text" : "text/plain",
928 "html" : "text/html",
935 "html" : "text/html",
929 "svg" : "image/svg+xml",
936 "svg" : "image/svg+xml",
930 "png" : "image/png",
937 "png" : "image/png",
931 "jpeg" : "image/jpeg",
938 "jpeg" : "image/jpeg",
932 "latex" : "text/latex",
939 "latex" : "text/latex",
933 "json" : "application/json",
940 "json" : "application/json",
934 "javascript" : "application/javascript",
941 "javascript" : "application/javascript",
935 };
942 };
936
943
937 OutputArea.display_order = [
944 OutputArea.display_order = [
938 'application/javascript',
945 'application/javascript',
939 'text/html',
946 'text/html',
940 'text/markdown',
947 'text/markdown',
941 'text/latex',
948 'text/latex',
942 'image/svg+xml',
949 'image/svg+xml',
943 'image/png',
950 'image/png',
944 'image/jpeg',
951 'image/jpeg',
945 'application/pdf',
952 'application/pdf',
946 'text/plain'
953 'text/plain'
947 ];
954 ];
948
955
949 OutputArea.append_map = {
956 OutputArea.append_map = {
950 "text/plain" : append_text,
957 "text/plain" : append_text,
951 "text/html" : append_html,
958 "text/html" : append_html,
952 "text/markdown": append_markdown,
959 "text/markdown": append_markdown,
953 "image/svg+xml" : append_svg,
960 "image/svg+xml" : append_svg,
954 "image/png" : append_png,
961 "image/png" : append_png,
955 "image/jpeg" : append_jpeg,
962 "image/jpeg" : append_jpeg,
956 "text/latex" : append_latex,
963 "text/latex" : append_latex,
957 "application/javascript" : append_javascript,
964 "application/javascript" : append_javascript,
958 "application/pdf" : append_pdf
965 "application/pdf" : append_pdf
959 };
966 };
960
967
961 IPython.OutputArea = OutputArea;
968 IPython.OutputArea = OutputArea;
962
969
963 return IPython;
970 return IPython;
964
971
965 }(IPython));
972 }(IPython));
@@ -1,793 +1,809
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """An interactive kernel that talks to frontends over 0MQ."""
2 """An interactive kernel that talks to frontends over 0MQ."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import getpass
9 import sys
10 import sys
10 import time
11 import time
11 import traceback
12 import traceback
12 import logging
13 import logging
13 import uuid
14 import uuid
14
15
15 from datetime import datetime
16 from datetime import datetime
16 from signal import (
17 from signal import (
17 signal, default_int_handler, SIGINT
18 signal, default_int_handler, SIGINT
18 )
19 )
19
20
20 import zmq
21 import zmq
21 from zmq.eventloop import ioloop
22 from zmq.eventloop import ioloop
22 from zmq.eventloop.zmqstream import ZMQStream
23 from zmq.eventloop.zmqstream import ZMQStream
23
24
24 from IPython.config.configurable import Configurable
25 from IPython.config.configurable import Configurable
25 from IPython.core.error import StdinNotImplementedError
26 from IPython.core.error import StdinNotImplementedError
26 from IPython.core import release
27 from IPython.core import release
27 from IPython.utils import py3compat
28 from IPython.utils import py3compat
28 from IPython.utils.py3compat import builtin_mod, unicode_type, string_types
29 from IPython.utils.py3compat import builtin_mod, unicode_type, string_types
29 from IPython.utils.jsonutil import json_clean
30 from IPython.utils.jsonutil import json_clean
30 from IPython.utils.traitlets import (
31 from IPython.utils.traitlets import (
31 Any, Instance, Float, Dict, List, Set, Integer, Unicode,
32 Any, Instance, Float, Dict, List, Set, Integer, Unicode,
32 Type, Bool,
33 Type, Bool,
33 )
34 )
34
35
35 from .serialize import serialize_object, unpack_apply_message
36 from .serialize import serialize_object, unpack_apply_message
36 from .session import Session
37 from .session import Session
37 from .zmqshell import ZMQInteractiveShell
38 from .zmqshell import ZMQInteractiveShell
38
39
39
40
40 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
41 # Main kernel class
42 # Main kernel class
42 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
43
44
44 protocol_version = release.kernel_protocol_version
45 protocol_version = release.kernel_protocol_version
45 ipython_version = release.version
46 ipython_version = release.version
46 language_version = sys.version.split()[0]
47 language_version = sys.version.split()[0]
47
48
48
49
49 class Kernel(Configurable):
50 class Kernel(Configurable):
50
51
51 #---------------------------------------------------------------------------
52 #---------------------------------------------------------------------------
52 # Kernel interface
53 # Kernel interface
53 #---------------------------------------------------------------------------
54 #---------------------------------------------------------------------------
54
55
55 # attribute to override with a GUI
56 # attribute to override with a GUI
56 eventloop = Any(None)
57 eventloop = Any(None)
57 def _eventloop_changed(self, name, old, new):
58 def _eventloop_changed(self, name, old, new):
58 """schedule call to eventloop from IOLoop"""
59 """schedule call to eventloop from IOLoop"""
59 loop = ioloop.IOLoop.instance()
60 loop = ioloop.IOLoop.instance()
60 loop.add_callback(self.enter_eventloop)
61 loop.add_callback(self.enter_eventloop)
61
62
62 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
63 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
63 shell_class = Type(ZMQInteractiveShell)
64 shell_class = Type(ZMQInteractiveShell)
64
65
65 session = Instance(Session)
66 session = Instance(Session)
66 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
67 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
67 shell_streams = List()
68 shell_streams = List()
68 control_stream = Instance(ZMQStream)
69 control_stream = Instance(ZMQStream)
69 iopub_socket = Instance(zmq.Socket)
70 iopub_socket = Instance(zmq.Socket)
70 stdin_socket = Instance(zmq.Socket)
71 stdin_socket = Instance(zmq.Socket)
71 log = Instance(logging.Logger)
72 log = Instance(logging.Logger)
72
73
73 user_module = Any()
74 user_module = Any()
74 def _user_module_changed(self, name, old, new):
75 def _user_module_changed(self, name, old, new):
75 if self.shell is not None:
76 if self.shell is not None:
76 self.shell.user_module = new
77 self.shell.user_module = new
77
78
78 user_ns = Instance(dict, args=None, allow_none=True)
79 user_ns = Instance(dict, args=None, allow_none=True)
79 def _user_ns_changed(self, name, old, new):
80 def _user_ns_changed(self, name, old, new):
80 if self.shell is not None:
81 if self.shell is not None:
81 self.shell.user_ns = new
82 self.shell.user_ns = new
82 self.shell.init_user_ns()
83 self.shell.init_user_ns()
83
84
84 # identities:
85 # identities:
85 int_id = Integer(-1)
86 int_id = Integer(-1)
86 ident = Unicode()
87 ident = Unicode()
87
88
88 def _ident_default(self):
89 def _ident_default(self):
89 return unicode_type(uuid.uuid4())
90 return unicode_type(uuid.uuid4())
90
91
91 # Private interface
92 # Private interface
92
93
93 _darwin_app_nap = Bool(True, config=True,
94 _darwin_app_nap = Bool(True, config=True,
94 help="""Whether to use appnope for compatiblity with OS X App Nap.
95 help="""Whether to use appnope for compatiblity with OS X App Nap.
95
96
96 Only affects OS X >= 10.9.
97 Only affects OS X >= 10.9.
97 """
98 """
98 )
99 )
99
100
100 # Time to sleep after flushing the stdout/err buffers in each execute
101 # Time to sleep after flushing the stdout/err buffers in each execute
101 # cycle. While this introduces a hard limit on the minimal latency of the
102 # cycle. While this introduces a hard limit on the minimal latency of the
102 # execute cycle, it helps prevent output synchronization problems for
103 # execute cycle, it helps prevent output synchronization problems for
103 # clients.
104 # clients.
104 # Units are in seconds. The minimum zmq latency on local host is probably
105 # Units are in seconds. The minimum zmq latency on local host is probably
105 # ~150 microseconds, set this to 500us for now. We may need to increase it
106 # ~150 microseconds, set this to 500us for now. We may need to increase it
106 # a little if it's not enough after more interactive testing.
107 # a little if it's not enough after more interactive testing.
107 _execute_sleep = Float(0.0005, config=True)
108 _execute_sleep = Float(0.0005, config=True)
108
109
109 # Frequency of the kernel's event loop.
110 # Frequency of the kernel's event loop.
110 # Units are in seconds, kernel subclasses for GUI toolkits may need to
111 # Units are in seconds, kernel subclasses for GUI toolkits may need to
111 # adapt to milliseconds.
112 # adapt to milliseconds.
112 _poll_interval = Float(0.05, config=True)
113 _poll_interval = Float(0.05, config=True)
113
114
114 # If the shutdown was requested over the network, we leave here the
115 # If the shutdown was requested over the network, we leave here the
115 # necessary reply message so it can be sent by our registered atexit
116 # necessary reply message so it can be sent by our registered atexit
116 # handler. This ensures that the reply is only sent to clients truly at
117 # handler. This ensures that the reply is only sent to clients truly at
117 # the end of our shutdown process (which happens after the underlying
118 # the end of our shutdown process (which happens after the underlying
118 # IPython shell's own shutdown).
119 # IPython shell's own shutdown).
119 _shutdown_message = None
120 _shutdown_message = None
120
121
121 # This is a dict of port number that the kernel is listening on. It is set
122 # This is a dict of port number that the kernel is listening on. It is set
122 # by record_ports and used by connect_request.
123 # by record_ports and used by connect_request.
123 _recorded_ports = Dict()
124 _recorded_ports = Dict()
124
125
125 # A reference to the Python builtin 'raw_input' function.
126 # A reference to the Python builtin 'raw_input' function.
126 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
127 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
127 _sys_raw_input = Any()
128 _sys_raw_input = Any()
128 _sys_eval_input = Any()
129 _sys_eval_input = Any()
129
130
130 # set of aborted msg_ids
131 # set of aborted msg_ids
131 aborted = Set()
132 aborted = Set()
132
133
133
134
134 def __init__(self, **kwargs):
135 def __init__(self, **kwargs):
135 super(Kernel, self).__init__(**kwargs)
136 super(Kernel, self).__init__(**kwargs)
136
137
137 # Initialize the InteractiveShell subclass
138 # Initialize the InteractiveShell subclass
138 self.shell = self.shell_class.instance(parent=self,
139 self.shell = self.shell_class.instance(parent=self,
139 profile_dir = self.profile_dir,
140 profile_dir = self.profile_dir,
140 user_module = self.user_module,
141 user_module = self.user_module,
141 user_ns = self.user_ns,
142 user_ns = self.user_ns,
142 kernel = self,
143 kernel = self,
143 )
144 )
144 self.shell.displayhook.session = self.session
145 self.shell.displayhook.session = self.session
145 self.shell.displayhook.pub_socket = self.iopub_socket
146 self.shell.displayhook.pub_socket = self.iopub_socket
146 self.shell.displayhook.topic = self._topic('execute_result')
147 self.shell.displayhook.topic = self._topic('execute_result')
147 self.shell.display_pub.session = self.session
148 self.shell.display_pub.session = self.session
148 self.shell.display_pub.pub_socket = self.iopub_socket
149 self.shell.display_pub.pub_socket = self.iopub_socket
149 self.shell.data_pub.session = self.session
150 self.shell.data_pub.session = self.session
150 self.shell.data_pub.pub_socket = self.iopub_socket
151 self.shell.data_pub.pub_socket = self.iopub_socket
151
152
152 # TMP - hack while developing
153 # TMP - hack while developing
153 self.shell._reply_content = None
154 self.shell._reply_content = None
154
155
155 # Build dict of handlers for message types
156 # Build dict of handlers for message types
156 msg_types = [ 'execute_request', 'complete_request',
157 msg_types = [ 'execute_request', 'complete_request',
157 'object_info_request', 'history_request',
158 'object_info_request', 'history_request',
158 'kernel_info_request',
159 'kernel_info_request',
159 'connect_request', 'shutdown_request',
160 'connect_request', 'shutdown_request',
160 'apply_request',
161 'apply_request',
161 ]
162 ]
162 self.shell_handlers = {}
163 self.shell_handlers = {}
163 for msg_type in msg_types:
164 for msg_type in msg_types:
164 self.shell_handlers[msg_type] = getattr(self, msg_type)
165 self.shell_handlers[msg_type] = getattr(self, msg_type)
165
166
166 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
167 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
167 comm_manager = self.shell.comm_manager
168 comm_manager = self.shell.comm_manager
168 for msg_type in comm_msg_types:
169 for msg_type in comm_msg_types:
169 self.shell_handlers[msg_type] = getattr(comm_manager, msg_type)
170 self.shell_handlers[msg_type] = getattr(comm_manager, msg_type)
170
171
171 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
172 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
172 self.control_handlers = {}
173 self.control_handlers = {}
173 for msg_type in control_msg_types:
174 for msg_type in control_msg_types:
174 self.control_handlers[msg_type] = getattr(self, msg_type)
175 self.control_handlers[msg_type] = getattr(self, msg_type)
175
176
176
177
177 def dispatch_control(self, msg):
178 def dispatch_control(self, msg):
178 """dispatch control requests"""
179 """dispatch control requests"""
179 idents,msg = self.session.feed_identities(msg, copy=False)
180 idents,msg = self.session.feed_identities(msg, copy=False)
180 try:
181 try:
181 msg = self.session.unserialize(msg, content=True, copy=False)
182 msg = self.session.unserialize(msg, content=True, copy=False)
182 except:
183 except:
183 self.log.error("Invalid Control Message", exc_info=True)
184 self.log.error("Invalid Control Message", exc_info=True)
184 return
185 return
185
186
186 self.log.debug("Control received: %s", msg)
187 self.log.debug("Control received: %s", msg)
187
188
188 header = msg['header']
189 header = msg['header']
189 msg_id = header['msg_id']
190 msg_id = header['msg_id']
190 msg_type = header['msg_type']
191 msg_type = header['msg_type']
191
192
192 handler = self.control_handlers.get(msg_type, None)
193 handler = self.control_handlers.get(msg_type, None)
193 if handler is None:
194 if handler is None:
194 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
195 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
195 else:
196 else:
196 try:
197 try:
197 handler(self.control_stream, idents, msg)
198 handler(self.control_stream, idents, msg)
198 except Exception:
199 except Exception:
199 self.log.error("Exception in control handler:", exc_info=True)
200 self.log.error("Exception in control handler:", exc_info=True)
200
201
201 def dispatch_shell(self, stream, msg):
202 def dispatch_shell(self, stream, msg):
202 """dispatch shell requests"""
203 """dispatch shell requests"""
203 # flush control requests first
204 # flush control requests first
204 if self.control_stream:
205 if self.control_stream:
205 self.control_stream.flush()
206 self.control_stream.flush()
206
207
207 idents,msg = self.session.feed_identities(msg, copy=False)
208 idents,msg = self.session.feed_identities(msg, copy=False)
208 try:
209 try:
209 msg = self.session.unserialize(msg, content=True, copy=False)
210 msg = self.session.unserialize(msg, content=True, copy=False)
210 except:
211 except:
211 self.log.error("Invalid Message", exc_info=True)
212 self.log.error("Invalid Message", exc_info=True)
212 return
213 return
213
214
214 header = msg['header']
215 header = msg['header']
215 msg_id = header['msg_id']
216 msg_id = header['msg_id']
216 msg_type = msg['header']['msg_type']
217 msg_type = msg['header']['msg_type']
217
218
218 # Print some info about this message and leave a '--->' marker, so it's
219 # Print some info about this message and leave a '--->' marker, so it's
219 # easier to trace visually the message chain when debugging. Each
220 # easier to trace visually the message chain when debugging. Each
220 # handler prints its message at the end.
221 # handler prints its message at the end.
221 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
222 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
222 self.log.debug(' Content: %s\n --->\n ', msg['content'])
223 self.log.debug(' Content: %s\n --->\n ', msg['content'])
223
224
224 if msg_id in self.aborted:
225 if msg_id in self.aborted:
225 self.aborted.remove(msg_id)
226 self.aborted.remove(msg_id)
226 # is it safe to assume a msg_id will not be resubmitted?
227 # is it safe to assume a msg_id will not be resubmitted?
227 reply_type = msg_type.split('_')[0] + '_reply'
228 reply_type = msg_type.split('_')[0] + '_reply'
228 status = {'status' : 'aborted'}
229 status = {'status' : 'aborted'}
229 md = {'engine' : self.ident}
230 md = {'engine' : self.ident}
230 md.update(status)
231 md.update(status)
231 reply_msg = self.session.send(stream, reply_type, metadata=md,
232 reply_msg = self.session.send(stream, reply_type, metadata=md,
232 content=status, parent=msg, ident=idents)
233 content=status, parent=msg, ident=idents)
233 return
234 return
234
235
235 handler = self.shell_handlers.get(msg_type, None)
236 handler = self.shell_handlers.get(msg_type, None)
236 if handler is None:
237 if handler is None:
237 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
238 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
238 else:
239 else:
239 # ensure default_int_handler during handler call
240 # ensure default_int_handler during handler call
240 sig = signal(SIGINT, default_int_handler)
241 sig = signal(SIGINT, default_int_handler)
241 try:
242 try:
242 handler(stream, idents, msg)
243 handler(stream, idents, msg)
243 except Exception:
244 except Exception:
244 self.log.error("Exception in message handler:", exc_info=True)
245 self.log.error("Exception in message handler:", exc_info=True)
245 finally:
246 finally:
246 signal(SIGINT, sig)
247 signal(SIGINT, sig)
247
248
248 def enter_eventloop(self):
249 def enter_eventloop(self):
249 """enter eventloop"""
250 """enter eventloop"""
250 self.log.info("entering eventloop %s", self.eventloop)
251 self.log.info("entering eventloop %s", self.eventloop)
251 for stream in self.shell_streams:
252 for stream in self.shell_streams:
252 # flush any pending replies,
253 # flush any pending replies,
253 # which may be skipped by entering the eventloop
254 # which may be skipped by entering the eventloop
254 stream.flush(zmq.POLLOUT)
255 stream.flush(zmq.POLLOUT)
255 # restore default_int_handler
256 # restore default_int_handler
256 signal(SIGINT, default_int_handler)
257 signal(SIGINT, default_int_handler)
257 while self.eventloop is not None:
258 while self.eventloop is not None:
258 try:
259 try:
259 self.eventloop(self)
260 self.eventloop(self)
260 except KeyboardInterrupt:
261 except KeyboardInterrupt:
261 # Ctrl-C shouldn't crash the kernel
262 # Ctrl-C shouldn't crash the kernel
262 self.log.error("KeyboardInterrupt caught in kernel")
263 self.log.error("KeyboardInterrupt caught in kernel")
263 continue
264 continue
264 else:
265 else:
265 # eventloop exited cleanly, this means we should stop (right?)
266 # eventloop exited cleanly, this means we should stop (right?)
266 self.eventloop = None
267 self.eventloop = None
267 break
268 break
268 self.log.info("exiting eventloop")
269 self.log.info("exiting eventloop")
269
270
270 def start(self):
271 def start(self):
271 """register dispatchers for streams"""
272 """register dispatchers for streams"""
272 self.shell.exit_now = False
273 self.shell.exit_now = False
273 if self.control_stream:
274 if self.control_stream:
274 self.control_stream.on_recv(self.dispatch_control, copy=False)
275 self.control_stream.on_recv(self.dispatch_control, copy=False)
275
276
276 def make_dispatcher(stream):
277 def make_dispatcher(stream):
277 def dispatcher(msg):
278 def dispatcher(msg):
278 return self.dispatch_shell(stream, msg)
279 return self.dispatch_shell(stream, msg)
279 return dispatcher
280 return dispatcher
280
281
281 for s in self.shell_streams:
282 for s in self.shell_streams:
282 s.on_recv(make_dispatcher(s), copy=False)
283 s.on_recv(make_dispatcher(s), copy=False)
283
284
284 # publish idle status
285 # publish idle status
285 self._publish_status('starting')
286 self._publish_status('starting')
286
287
287 def do_one_iteration(self):
288 def do_one_iteration(self):
288 """step eventloop just once"""
289 """step eventloop just once"""
289 if self.control_stream:
290 if self.control_stream:
290 self.control_stream.flush()
291 self.control_stream.flush()
291 for stream in self.shell_streams:
292 for stream in self.shell_streams:
292 # handle at most one request per iteration
293 # handle at most one request per iteration
293 stream.flush(zmq.POLLIN, 1)
294 stream.flush(zmq.POLLIN, 1)
294 stream.flush(zmq.POLLOUT)
295 stream.flush(zmq.POLLOUT)
295
296
296
297
297 def record_ports(self, ports):
298 def record_ports(self, ports):
298 """Record the ports that this kernel is using.
299 """Record the ports that this kernel is using.
299
300
300 The creator of the Kernel instance must call this methods if they
301 The creator of the Kernel instance must call this methods if they
301 want the :meth:`connect_request` method to return the port numbers.
302 want the :meth:`connect_request` method to return the port numbers.
302 """
303 """
303 self._recorded_ports = ports
304 self._recorded_ports = ports
304
305
305 #---------------------------------------------------------------------------
306 #---------------------------------------------------------------------------
306 # Kernel request handlers
307 # Kernel request handlers
307 #---------------------------------------------------------------------------
308 #---------------------------------------------------------------------------
308
309
309 def _make_metadata(self, other=None):
310 def _make_metadata(self, other=None):
310 """init metadata dict, for execute/apply_reply"""
311 """init metadata dict, for execute/apply_reply"""
311 new_md = {
312 new_md = {
312 'dependencies_met' : True,
313 'dependencies_met' : True,
313 'engine' : self.ident,
314 'engine' : self.ident,
314 'started': datetime.now(),
315 'started': datetime.now(),
315 }
316 }
316 if other:
317 if other:
317 new_md.update(other)
318 new_md.update(other)
318 return new_md
319 return new_md
319
320
320 def _publish_execute_input(self, code, parent, execution_count):
321 def _publish_execute_input(self, code, parent, execution_count):
321 """Publish the code request on the iopub stream."""
322 """Publish the code request on the iopub stream."""
322
323
323 self.session.send(self.iopub_socket, u'execute_input',
324 self.session.send(self.iopub_socket, u'execute_input',
324 {u'code':code, u'execution_count': execution_count},
325 {u'code':code, u'execution_count': execution_count},
325 parent=parent, ident=self._topic('execute_input')
326 parent=parent, ident=self._topic('execute_input')
326 )
327 )
327
328
328 def _publish_status(self, status, parent=None):
329 def _publish_status(self, status, parent=None):
329 """send status (busy/idle) on IOPub"""
330 """send status (busy/idle) on IOPub"""
330 self.session.send(self.iopub_socket,
331 self.session.send(self.iopub_socket,
331 u'status',
332 u'status',
332 {u'execution_state': status},
333 {u'execution_state': status},
333 parent=parent,
334 parent=parent,
334 ident=self._topic('status'),
335 ident=self._topic('status'),
335 )
336 )
337
338 def _forward_raw_input(self, ident, parent, allow_stdin):
339 """Replace raw_input. Note that is not sufficient to replace
340 raw_input in the user namespace.
341 """
342
343 if allow_stdin:
344 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
345 input = lambda prompt='': eval(raw_input(prompt))
346 _getpass = lambda prompt='', stream=None: self._raw_input(
347 prompt, ident, parent, password=True
348 )
349 else:
350 _getpass = raw_input = input = lambda prompt='' : self._no_raw_input()
351
352
353 if py3compat.PY3:
354 self._sys_raw_input = builtin_mod.input
355 builtin_mod.input = raw_input
356 else:
357 self._sys_raw_input = builtin_mod.raw_input
358 self._sys_eval_input = builtin_mod.input
359 builtin_mod.raw_input = raw_input
360 builtin_mod.input = input
361 self._save_getpass = getpass.getpass
362 getpass.getpass = _getpass
363
364 def _restore_raw_input(self):
365 # Restore raw_input
366 if py3compat.PY3:
367 builtin_mod.input = self._sys_raw_input
368 else:
369 builtin_mod.raw_input = self._sys_raw_input
370 builtin_mod.input = self._sys_eval_input
336
371
372 getpass.getpass = self._save_getpass
337
373
338 def execute_request(self, stream, ident, parent):
374 def execute_request(self, stream, ident, parent):
339 """handle an execute_request"""
375 """handle an execute_request"""
340
376
341 self._publish_status(u'busy', parent)
377 self._publish_status(u'busy', parent)
342
378
343 try:
379 try:
344 content = parent[u'content']
380 content = parent[u'content']
345 code = py3compat.cast_unicode_py2(content[u'code'])
381 code = py3compat.cast_unicode_py2(content[u'code'])
346 silent = content[u'silent']
382 silent = content[u'silent']
347 store_history = content.get(u'store_history', not silent)
383 store_history = content.get(u'store_history', not silent)
348 except:
384 except:
349 self.log.error("Got bad msg: ")
385 self.log.error("Got bad msg: ")
350 self.log.error("%s", parent)
386 self.log.error("%s", parent)
351 return
387 return
352
388
353 md = self._make_metadata(parent['metadata'])
389 md = self._make_metadata(parent['metadata'])
354
390
355 shell = self.shell # we'll need this a lot here
391 shell = self.shell # we'll need this a lot here
356
392
357 # Replace raw_input. Note that is not sufficient to replace
393 self._forward_raw_input(ident, parent, content.get('allow_stdin', False))
358 # raw_input in the user namespace.
359 if content.get('allow_stdin', False):
360 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
361 input = lambda prompt='': eval(raw_input(prompt))
362 else:
363 raw_input = input = lambda prompt='' : self._no_raw_input()
364
365 if py3compat.PY3:
366 self._sys_raw_input = builtin_mod.input
367 builtin_mod.input = raw_input
368 else:
369 self._sys_raw_input = builtin_mod.raw_input
370 self._sys_eval_input = builtin_mod.input
371 builtin_mod.raw_input = raw_input
372 builtin_mod.input = input
373
394
374 # Set the parent message of the display hook and out streams.
395 # Set the parent message of the display hook and out streams.
375 shell.set_parent(parent)
396 shell.set_parent(parent)
376
397
377 # Re-broadcast our input for the benefit of listening clients, and
398 # Re-broadcast our input for the benefit of listening clients, and
378 # start computing output
399 # start computing output
379 if not silent:
400 if not silent:
380 self._publish_execute_input(code, parent, shell.execution_count)
401 self._publish_execute_input(code, parent, shell.execution_count)
381
402
382 reply_content = {}
403 reply_content = {}
383 # FIXME: the shell calls the exception handler itself.
404 # FIXME: the shell calls the exception handler itself.
384 shell._reply_content = None
405 shell._reply_content = None
385 try:
406 try:
386 shell.run_cell(code, store_history=store_history, silent=silent)
407 shell.run_cell(code, store_history=store_history, silent=silent)
387 except:
408 except:
388 status = u'error'
409 status = u'error'
389 # FIXME: this code right now isn't being used yet by default,
410 # FIXME: this code right now isn't being used yet by default,
390 # because the run_cell() call above directly fires off exception
411 # because the run_cell() call above directly fires off exception
391 # reporting. This code, therefore, is only active in the scenario
412 # reporting. This code, therefore, is only active in the scenario
392 # where runlines itself has an unhandled exception. We need to
413 # where runlines itself has an unhandled exception. We need to
393 # uniformize this, for all exception construction to come from a
414 # uniformize this, for all exception construction to come from a
394 # single location in the codbase.
415 # single location in the codbase.
395 etype, evalue, tb = sys.exc_info()
416 etype, evalue, tb = sys.exc_info()
396 tb_list = traceback.format_exception(etype, evalue, tb)
417 tb_list = traceback.format_exception(etype, evalue, tb)
397 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
418 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
398 else:
419 else:
399 status = u'ok'
420 status = u'ok'
400 finally:
421 finally:
401 # Restore raw_input.
422 self._restore_raw_input()
402 if py3compat.PY3:
403 builtin_mod.input = self._sys_raw_input
404 else:
405 builtin_mod.raw_input = self._sys_raw_input
406 builtin_mod.input = self._sys_eval_input
407
423
408 reply_content[u'status'] = status
424 reply_content[u'status'] = status
409
425
410 # Return the execution counter so clients can display prompts
426 # Return the execution counter so clients can display prompts
411 reply_content['execution_count'] = shell.execution_count - 1
427 reply_content['execution_count'] = shell.execution_count - 1
412
428
413 # FIXME - fish exception info out of shell, possibly left there by
429 # FIXME - fish exception info out of shell, possibly left there by
414 # runlines. We'll need to clean up this logic later.
430 # runlines. We'll need to clean up this logic later.
415 if shell._reply_content is not None:
431 if shell._reply_content is not None:
416 reply_content.update(shell._reply_content)
432 reply_content.update(shell._reply_content)
417 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
433 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
418 reply_content['engine_info'] = e_info
434 reply_content['engine_info'] = e_info
419 # reset after use
435 # reset after use
420 shell._reply_content = None
436 shell._reply_content = None
421
437
422 if 'traceback' in reply_content:
438 if 'traceback' in reply_content:
423 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
439 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
424
440
425
441
426 # At this point, we can tell whether the main code execution succeeded
442 # At this point, we can tell whether the main code execution succeeded
427 # or not. If it did, we proceed to evaluate user_expressions
443 # or not. If it did, we proceed to evaluate user_expressions
428 if reply_content['status'] == 'ok':
444 if reply_content['status'] == 'ok':
429 reply_content[u'user_expressions'] = \
445 reply_content[u'user_expressions'] = \
430 shell.user_expressions(content.get(u'user_expressions', {}))
446 shell.user_expressions(content.get(u'user_expressions', {}))
431 else:
447 else:
432 # If there was an error, don't even try to compute expressions
448 # If there was an error, don't even try to compute expressions
433 reply_content[u'user_expressions'] = {}
449 reply_content[u'user_expressions'] = {}
434
450
435 # Payloads should be retrieved regardless of outcome, so we can both
451 # Payloads should be retrieved regardless of outcome, so we can both
436 # recover partial output (that could have been generated early in a
452 # recover partial output (that could have been generated early in a
437 # block, before an error) and clear the payload system always.
453 # block, before an error) and clear the payload system always.
438 reply_content[u'payload'] = shell.payload_manager.read_payload()
454 reply_content[u'payload'] = shell.payload_manager.read_payload()
439 # Be agressive about clearing the payload because we don't want
455 # Be agressive about clearing the payload because we don't want
440 # it to sit in memory until the next execute_request comes in.
456 # it to sit in memory until the next execute_request comes in.
441 shell.payload_manager.clear_payload()
457 shell.payload_manager.clear_payload()
442
458
443 # Flush output before sending the reply.
459 # Flush output before sending the reply.
444 sys.stdout.flush()
460 sys.stdout.flush()
445 sys.stderr.flush()
461 sys.stderr.flush()
446 # FIXME: on rare occasions, the flush doesn't seem to make it to the
462 # FIXME: on rare occasions, the flush doesn't seem to make it to the
447 # clients... This seems to mitigate the problem, but we definitely need
463 # clients... This seems to mitigate the problem, but we definitely need
448 # to better understand what's going on.
464 # to better understand what's going on.
449 if self._execute_sleep:
465 if self._execute_sleep:
450 time.sleep(self._execute_sleep)
466 time.sleep(self._execute_sleep)
451
467
452 # Send the reply.
468 # Send the reply.
453 reply_content = json_clean(reply_content)
469 reply_content = json_clean(reply_content)
454
470
455 md['status'] = reply_content['status']
471 md['status'] = reply_content['status']
456 if reply_content['status'] == 'error' and \
472 if reply_content['status'] == 'error' and \
457 reply_content['ename'] == 'UnmetDependency':
473 reply_content['ename'] == 'UnmetDependency':
458 md['dependencies_met'] = False
474 md['dependencies_met'] = False
459
475
460 reply_msg = self.session.send(stream, u'execute_reply',
476 reply_msg = self.session.send(stream, u'execute_reply',
461 reply_content, parent, metadata=md,
477 reply_content, parent, metadata=md,
462 ident=ident)
478 ident=ident)
463
479
464 self.log.debug("%s", reply_msg)
480 self.log.debug("%s", reply_msg)
465
481
466 if not silent and reply_msg['content']['status'] == u'error':
482 if not silent and reply_msg['content']['status'] == u'error':
467 self._abort_queues()
483 self._abort_queues()
468
484
469 self._publish_status(u'idle', parent)
485 self._publish_status(u'idle', parent)
470
486
471 def complete_request(self, stream, ident, parent):
487 def complete_request(self, stream, ident, parent):
472 txt, matches = self._complete(parent)
488 txt, matches = self._complete(parent)
473 matches = {'matches' : matches,
489 matches = {'matches' : matches,
474 'matched_text' : txt,
490 'matched_text' : txt,
475 'status' : 'ok'}
491 'status' : 'ok'}
476 matches = json_clean(matches)
492 matches = json_clean(matches)
477 completion_msg = self.session.send(stream, 'complete_reply',
493 completion_msg = self.session.send(stream, 'complete_reply',
478 matches, parent, ident)
494 matches, parent, ident)
479 self.log.debug("%s", completion_msg)
495 self.log.debug("%s", completion_msg)
480
496
481 def object_info_request(self, stream, ident, parent):
497 def object_info_request(self, stream, ident, parent):
482 content = parent['content']
498 content = parent['content']
483 object_info = self.shell.object_inspect(content['oname'],
499 object_info = self.shell.object_inspect(content['oname'],
484 detail_level = content.get('detail_level', 0)
500 detail_level = content.get('detail_level', 0)
485 )
501 )
486 # Before we send this object over, we scrub it for JSON usage
502 # Before we send this object over, we scrub it for JSON usage
487 oinfo = json_clean(object_info)
503 oinfo = json_clean(object_info)
488 msg = self.session.send(stream, 'object_info_reply',
504 msg = self.session.send(stream, 'object_info_reply',
489 oinfo, parent, ident)
505 oinfo, parent, ident)
490 self.log.debug("%s", msg)
506 self.log.debug("%s", msg)
491
507
492 def history_request(self, stream, ident, parent):
508 def history_request(self, stream, ident, parent):
493 # We need to pull these out, as passing **kwargs doesn't work with
509 # We need to pull these out, as passing **kwargs doesn't work with
494 # unicode keys before Python 2.6.5.
510 # unicode keys before Python 2.6.5.
495 hist_access_type = parent['content']['hist_access_type']
511 hist_access_type = parent['content']['hist_access_type']
496 raw = parent['content']['raw']
512 raw = parent['content']['raw']
497 output = parent['content']['output']
513 output = parent['content']['output']
498 if hist_access_type == 'tail':
514 if hist_access_type == 'tail':
499 n = parent['content']['n']
515 n = parent['content']['n']
500 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
516 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
501 include_latest=True)
517 include_latest=True)
502
518
503 elif hist_access_type == 'range':
519 elif hist_access_type == 'range':
504 session = parent['content']['session']
520 session = parent['content']['session']
505 start = parent['content']['start']
521 start = parent['content']['start']
506 stop = parent['content']['stop']
522 stop = parent['content']['stop']
507 hist = self.shell.history_manager.get_range(session, start, stop,
523 hist = self.shell.history_manager.get_range(session, start, stop,
508 raw=raw, output=output)
524 raw=raw, output=output)
509
525
510 elif hist_access_type == 'search':
526 elif hist_access_type == 'search':
511 n = parent['content'].get('n')
527 n = parent['content'].get('n')
512 unique = parent['content'].get('unique', False)
528 unique = parent['content'].get('unique', False)
513 pattern = parent['content']['pattern']
529 pattern = parent['content']['pattern']
514 hist = self.shell.history_manager.search(
530 hist = self.shell.history_manager.search(
515 pattern, raw=raw, output=output, n=n, unique=unique)
531 pattern, raw=raw, output=output, n=n, unique=unique)
516
532
517 else:
533 else:
518 hist = []
534 hist = []
519 hist = list(hist)
535 hist = list(hist)
520 content = {'history' : hist}
536 content = {'history' : hist}
521 content = json_clean(content)
537 content = json_clean(content)
522 msg = self.session.send(stream, 'history_reply',
538 msg = self.session.send(stream, 'history_reply',
523 content, parent, ident)
539 content, parent, ident)
524 self.log.debug("Sending history reply with %i entries", len(hist))
540 self.log.debug("Sending history reply with %i entries", len(hist))
525
541
526 def connect_request(self, stream, ident, parent):
542 def connect_request(self, stream, ident, parent):
527 if self._recorded_ports is not None:
543 if self._recorded_ports is not None:
528 content = self._recorded_ports.copy()
544 content = self._recorded_ports.copy()
529 else:
545 else:
530 content = {}
546 content = {}
531 msg = self.session.send(stream, 'connect_reply',
547 msg = self.session.send(stream, 'connect_reply',
532 content, parent, ident)
548 content, parent, ident)
533 self.log.debug("%s", msg)
549 self.log.debug("%s", msg)
534
550
535 def kernel_info_request(self, stream, ident, parent):
551 def kernel_info_request(self, stream, ident, parent):
536 vinfo = {
552 vinfo = {
537 'protocol_version': protocol_version,
553 'protocol_version': protocol_version,
538 'ipython_version': ipython_version,
554 'ipython_version': ipython_version,
539 'language_version': language_version,
555 'language_version': language_version,
540 'language': 'python',
556 'language': 'python',
541 }
557 }
542 msg = self.session.send(stream, 'kernel_info_reply',
558 msg = self.session.send(stream, 'kernel_info_reply',
543 vinfo, parent, ident)
559 vinfo, parent, ident)
544 self.log.debug("%s", msg)
560 self.log.debug("%s", msg)
545
561
546 def shutdown_request(self, stream, ident, parent):
562 def shutdown_request(self, stream, ident, parent):
547 self.shell.exit_now = True
563 self.shell.exit_now = True
548 content = dict(status='ok')
564 content = dict(status='ok')
549 content.update(parent['content'])
565 content.update(parent['content'])
550 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
566 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
551 # same content, but different msg_id for broadcasting on IOPub
567 # same content, but different msg_id for broadcasting on IOPub
552 self._shutdown_message = self.session.msg(u'shutdown_reply',
568 self._shutdown_message = self.session.msg(u'shutdown_reply',
553 content, parent
569 content, parent
554 )
570 )
555
571
556 self._at_shutdown()
572 self._at_shutdown()
557 # call sys.exit after a short delay
573 # call sys.exit after a short delay
558 loop = ioloop.IOLoop.instance()
574 loop = ioloop.IOLoop.instance()
559 loop.add_timeout(time.time()+0.1, loop.stop)
575 loop.add_timeout(time.time()+0.1, loop.stop)
560
576
561 #---------------------------------------------------------------------------
577 #---------------------------------------------------------------------------
562 # Engine methods
578 # Engine methods
563 #---------------------------------------------------------------------------
579 #---------------------------------------------------------------------------
564
580
565 def apply_request(self, stream, ident, parent):
581 def apply_request(self, stream, ident, parent):
566 try:
582 try:
567 content = parent[u'content']
583 content = parent[u'content']
568 bufs = parent[u'buffers']
584 bufs = parent[u'buffers']
569 msg_id = parent['header']['msg_id']
585 msg_id = parent['header']['msg_id']
570 except:
586 except:
571 self.log.error("Got bad msg: %s", parent, exc_info=True)
587 self.log.error("Got bad msg: %s", parent, exc_info=True)
572 return
588 return
573
589
574 self._publish_status(u'busy', parent)
590 self._publish_status(u'busy', parent)
575
591
576 # Set the parent message of the display hook and out streams.
592 # Set the parent message of the display hook and out streams.
577 shell = self.shell
593 shell = self.shell
578 shell.set_parent(parent)
594 shell.set_parent(parent)
579
595
580 # execute_input_msg = self.session.msg(u'execute_input',{u'code':code}, parent=parent)
596 # execute_input_msg = self.session.msg(u'execute_input',{u'code':code}, parent=parent)
581 # self.iopub_socket.send(execute_input_msg)
597 # self.iopub_socket.send(execute_input_msg)
582 # self.session.send(self.iopub_socket, u'execute_input', {u'code':code},parent=parent)
598 # self.session.send(self.iopub_socket, u'execute_input', {u'code':code},parent=parent)
583 md = self._make_metadata(parent['metadata'])
599 md = self._make_metadata(parent['metadata'])
584 try:
600 try:
585 working = shell.user_ns
601 working = shell.user_ns
586
602
587 prefix = "_"+str(msg_id).replace("-","")+"_"
603 prefix = "_"+str(msg_id).replace("-","")+"_"
588
604
589 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
605 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
590
606
591 fname = getattr(f, '__name__', 'f')
607 fname = getattr(f, '__name__', 'f')
592
608
593 fname = prefix+"f"
609 fname = prefix+"f"
594 argname = prefix+"args"
610 argname = prefix+"args"
595 kwargname = prefix+"kwargs"
611 kwargname = prefix+"kwargs"
596 resultname = prefix+"result"
612 resultname = prefix+"result"
597
613
598 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
614 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
599 # print ns
615 # print ns
600 working.update(ns)
616 working.update(ns)
601 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
617 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
602 try:
618 try:
603 exec(code, shell.user_global_ns, shell.user_ns)
619 exec(code, shell.user_global_ns, shell.user_ns)
604 result = working.get(resultname)
620 result = working.get(resultname)
605 finally:
621 finally:
606 for key in ns:
622 for key in ns:
607 working.pop(key)
623 working.pop(key)
608
624
609 result_buf = serialize_object(result,
625 result_buf = serialize_object(result,
610 buffer_threshold=self.session.buffer_threshold,
626 buffer_threshold=self.session.buffer_threshold,
611 item_threshold=self.session.item_threshold,
627 item_threshold=self.session.item_threshold,
612 )
628 )
613
629
614 except:
630 except:
615 # invoke IPython traceback formatting
631 # invoke IPython traceback formatting
616 shell.showtraceback()
632 shell.showtraceback()
617 # FIXME - fish exception info out of shell, possibly left there by
633 # FIXME - fish exception info out of shell, possibly left there by
618 # run_code. We'll need to clean up this logic later.
634 # run_code. We'll need to clean up this logic later.
619 reply_content = {}
635 reply_content = {}
620 if shell._reply_content is not None:
636 if shell._reply_content is not None:
621 reply_content.update(shell._reply_content)
637 reply_content.update(shell._reply_content)
622 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
638 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
623 reply_content['engine_info'] = e_info
639 reply_content['engine_info'] = e_info
624 # reset after use
640 # reset after use
625 shell._reply_content = None
641 shell._reply_content = None
626
642
627 self.session.send(self.iopub_socket, u'error', reply_content, parent=parent,
643 self.session.send(self.iopub_socket, u'error', reply_content, parent=parent,
628 ident=self._topic('error'))
644 ident=self._topic('error'))
629 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
645 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
630 result_buf = []
646 result_buf = []
631
647
632 if reply_content['ename'] == 'UnmetDependency':
648 if reply_content['ename'] == 'UnmetDependency':
633 md['dependencies_met'] = False
649 md['dependencies_met'] = False
634 else:
650 else:
635 reply_content = {'status' : 'ok'}
651 reply_content = {'status' : 'ok'}
636
652
637 # put 'ok'/'error' status in header, for scheduler introspection:
653 # put 'ok'/'error' status in header, for scheduler introspection:
638 md['status'] = reply_content['status']
654 md['status'] = reply_content['status']
639
655
640 # flush i/o
656 # flush i/o
641 sys.stdout.flush()
657 sys.stdout.flush()
642 sys.stderr.flush()
658 sys.stderr.flush()
643
659
644 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
660 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
645 parent=parent, ident=ident,buffers=result_buf, metadata=md)
661 parent=parent, ident=ident,buffers=result_buf, metadata=md)
646
662
647 self._publish_status(u'idle', parent)
663 self._publish_status(u'idle', parent)
648
664
649 #---------------------------------------------------------------------------
665 #---------------------------------------------------------------------------
650 # Control messages
666 # Control messages
651 #---------------------------------------------------------------------------
667 #---------------------------------------------------------------------------
652
668
653 def abort_request(self, stream, ident, parent):
669 def abort_request(self, stream, ident, parent):
654 """abort a specifig msg by id"""
670 """abort a specifig msg by id"""
655 msg_ids = parent['content'].get('msg_ids', None)
671 msg_ids = parent['content'].get('msg_ids', None)
656 if isinstance(msg_ids, string_types):
672 if isinstance(msg_ids, string_types):
657 msg_ids = [msg_ids]
673 msg_ids = [msg_ids]
658 if not msg_ids:
674 if not msg_ids:
659 self.abort_queues()
675 self.abort_queues()
660 for mid in msg_ids:
676 for mid in msg_ids:
661 self.aborted.add(str(mid))
677 self.aborted.add(str(mid))
662
678
663 content = dict(status='ok')
679 content = dict(status='ok')
664 reply_msg = self.session.send(stream, 'abort_reply', content=content,
680 reply_msg = self.session.send(stream, 'abort_reply', content=content,
665 parent=parent, ident=ident)
681 parent=parent, ident=ident)
666 self.log.debug("%s", reply_msg)
682 self.log.debug("%s", reply_msg)
667
683
668 def clear_request(self, stream, idents, parent):
684 def clear_request(self, stream, idents, parent):
669 """Clear our namespace."""
685 """Clear our namespace."""
670 self.shell.reset(False)
686 self.shell.reset(False)
671 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
687 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
672 content = dict(status='ok'))
688 content = dict(status='ok'))
673
689
674
690
675 #---------------------------------------------------------------------------
691 #---------------------------------------------------------------------------
676 # Protected interface
692 # Protected interface
677 #---------------------------------------------------------------------------
693 #---------------------------------------------------------------------------
678
694
679 def _wrap_exception(self, method=None):
695 def _wrap_exception(self, method=None):
680 # import here, because _wrap_exception is only used in parallel,
696 # import here, because _wrap_exception is only used in parallel,
681 # and parallel has higher min pyzmq version
697 # and parallel has higher min pyzmq version
682 from IPython.parallel.error import wrap_exception
698 from IPython.parallel.error import wrap_exception
683 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
699 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
684 content = wrap_exception(e_info)
700 content = wrap_exception(e_info)
685 return content
701 return content
686
702
687 def _topic(self, topic):
703 def _topic(self, topic):
688 """prefixed topic for IOPub messages"""
704 """prefixed topic for IOPub messages"""
689 if self.int_id >= 0:
705 if self.int_id >= 0:
690 base = "engine.%i" % self.int_id
706 base = "engine.%i" % self.int_id
691 else:
707 else:
692 base = "kernel.%s" % self.ident
708 base = "kernel.%s" % self.ident
693
709
694 return py3compat.cast_bytes("%s.%s" % (base, topic))
710 return py3compat.cast_bytes("%s.%s" % (base, topic))
695
711
696 def _abort_queues(self):
712 def _abort_queues(self):
697 for stream in self.shell_streams:
713 for stream in self.shell_streams:
698 if stream:
714 if stream:
699 self._abort_queue(stream)
715 self._abort_queue(stream)
700
716
701 def _abort_queue(self, stream):
717 def _abort_queue(self, stream):
702 poller = zmq.Poller()
718 poller = zmq.Poller()
703 poller.register(stream.socket, zmq.POLLIN)
719 poller.register(stream.socket, zmq.POLLIN)
704 while True:
720 while True:
705 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
721 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
706 if msg is None:
722 if msg is None:
707 return
723 return
708
724
709 self.log.info("Aborting:")
725 self.log.info("Aborting:")
710 self.log.info("%s", msg)
726 self.log.info("%s", msg)
711 msg_type = msg['header']['msg_type']
727 msg_type = msg['header']['msg_type']
712 reply_type = msg_type.split('_')[0] + '_reply'
728 reply_type = msg_type.split('_')[0] + '_reply'
713
729
714 status = {'status' : 'aborted'}
730 status = {'status' : 'aborted'}
715 md = {'engine' : self.ident}
731 md = {'engine' : self.ident}
716 md.update(status)
732 md.update(status)
717 reply_msg = self.session.send(stream, reply_type, metadata=md,
733 reply_msg = self.session.send(stream, reply_type, metadata=md,
718 content=status, parent=msg, ident=idents)
734 content=status, parent=msg, ident=idents)
719 self.log.debug("%s", reply_msg)
735 self.log.debug("%s", reply_msg)
720 # We need to wait a bit for requests to come in. This can probably
736 # We need to wait a bit for requests to come in. This can probably
721 # be set shorter for true asynchronous clients.
737 # be set shorter for true asynchronous clients.
722 poller.poll(50)
738 poller.poll(50)
723
739
724
740
725 def _no_raw_input(self):
741 def _no_raw_input(self):
726 """Raise StdinNotImplentedError if active frontend doesn't support
742 """Raise StdinNotImplentedError if active frontend doesn't support
727 stdin."""
743 stdin."""
728 raise StdinNotImplementedError("raw_input was called, but this "
744 raise StdinNotImplementedError("raw_input was called, but this "
729 "frontend does not support stdin.")
745 "frontend does not support stdin.")
730
746
731 def _raw_input(self, prompt, ident, parent):
747 def _raw_input(self, prompt, ident, parent, password=False):
732 # Flush output before making the request.
748 # Flush output before making the request.
733 sys.stderr.flush()
749 sys.stderr.flush()
734 sys.stdout.flush()
750 sys.stdout.flush()
735 # flush the stdin socket, to purge stale replies
751 # flush the stdin socket, to purge stale replies
736 while True:
752 while True:
737 try:
753 try:
738 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
754 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
739 except zmq.ZMQError as e:
755 except zmq.ZMQError as e:
740 if e.errno == zmq.EAGAIN:
756 if e.errno == zmq.EAGAIN:
741 break
757 break
742 else:
758 else:
743 raise
759 raise
744
760
745 # Send the input request.
761 # Send the input request.
746 content = json_clean(dict(prompt=prompt))
762 content = json_clean(dict(prompt=prompt, password=password))
747 self.session.send(self.stdin_socket, u'input_request', content, parent,
763 self.session.send(self.stdin_socket, u'input_request', content, parent,
748 ident=ident)
764 ident=ident)
749
765
750 # Await a response.
766 # Await a response.
751 while True:
767 while True:
752 try:
768 try:
753 ident, reply = self.session.recv(self.stdin_socket, 0)
769 ident, reply = self.session.recv(self.stdin_socket, 0)
754 except Exception:
770 except Exception:
755 self.log.warn("Invalid Message:", exc_info=True)
771 self.log.warn("Invalid Message:", exc_info=True)
756 except KeyboardInterrupt:
772 except KeyboardInterrupt:
757 # re-raise KeyboardInterrupt, to truncate traceback
773 # re-raise KeyboardInterrupt, to truncate traceback
758 raise KeyboardInterrupt
774 raise KeyboardInterrupt
759 else:
775 else:
760 break
776 break
761 try:
777 try:
762 value = py3compat.unicode_to_str(reply['content']['value'])
778 value = py3compat.unicode_to_str(reply['content']['value'])
763 except:
779 except:
764 self.log.error("Got bad raw_input reply: ")
780 self.log.error("Got bad raw_input reply: ")
765 self.log.error("%s", parent)
781 self.log.error("%s", parent)
766 value = ''
782 value = ''
767 if value == '\x04':
783 if value == '\x04':
768 # EOF
784 # EOF
769 raise EOFError
785 raise EOFError
770 return value
786 return value
771
787
772 def _complete(self, msg):
788 def _complete(self, msg):
773 c = msg['content']
789 c = msg['content']
774 try:
790 try:
775 cpos = int(c['cursor_pos'])
791 cpos = int(c['cursor_pos'])
776 except:
792 except:
777 # If we don't get something that we can convert to an integer, at
793 # If we don't get something that we can convert to an integer, at
778 # least attempt the completion guessing the cursor is at the end of
794 # least attempt the completion guessing the cursor is at the end of
779 # the text, if there's any, and otherwise of the line
795 # the text, if there's any, and otherwise of the line
780 cpos = len(c['text'])
796 cpos = len(c['text'])
781 if cpos==0:
797 if cpos==0:
782 cpos = len(c['line'])
798 cpos = len(c['line'])
783 return self.shell.complete(c['text'], c['line'], cpos)
799 return self.shell.complete(c['text'], c['line'], cpos)
784
800
785 def _at_shutdown(self):
801 def _at_shutdown(self):
786 """Actions taken at shutdown by the kernel, called by python's atexit.
802 """Actions taken at shutdown by the kernel, called by python's atexit.
787 """
803 """
788 # io.rprint("Kernel at_shutdown") # dbg
804 # io.rprint("Kernel at_shutdown") # dbg
789 if self._shutdown_message is not None:
805 if self._shutdown_message is not None:
790 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
806 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
791 self.log.debug("%s", self._shutdown_message)
807 self.log.debug("%s", self._shutdown_message)
792 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
808 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
793
809
General Comments 0
You need to be logged in to leave comments. Login now