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