##// END OF EJS Templates
put OutputArea map at the end
Matthias BUSSONNIER -
Show More
@@ -1,843 +1,843
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.clear_queued = null;
35 35 if (prompt_area === undefined) {
36 36 this.prompt_area = true;
37 37 } else {
38 38 this.prompt_area = prompt_area;
39 39 }
40 40 this.create_elements();
41 41 this.style();
42 42 this.bind_events();
43 43 };
44 44
45 /**
46 * Class properties
47 **/
48
49 /**
50 * Threshold to trigger autoscroll when the OutputArea is resized,
51 * typically when new outputs are added.
52 *
53 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
54 * unless it is < 0, in which case autoscroll will never be triggered
55 *
56 * @property auto_scroll_threshold
57 * @type Number
58 * @default 100
59 *
60 **/
61 OutputArea.auto_scroll_threshold = 100;
62
63 /**
64 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
65 * shorter than this are never scrolled.
66 *
67 * @property minimum_scroll_threshold
68 * @type Number
69 * @default 20
70 *
71 **/
72 OutputArea.minimum_scroll_threshold = 20;
73
74
75
76 OutputArea.mime_map = {
77 "text/plain" : "text",
78 "text/html" : "html",
79 "image/svg+xml" : "svg",
80 "image/png" : "png",
81 "image/jpeg" : "jpeg",
82 "text/latex" : "latex",
83 "application/json" : "json",
84 "application/javascript" : "javascript",
85 };
86
87 OutputArea.mime_map_r = {
88 "text" : "text/plain",
89 "html" : "text/html",
90 "svg" : "image/svg+xml",
91 "png" : "image/png",
92 "jpeg" : "image/jpeg",
93 "latex" : "text/latex",
94 "json" : "application/json",
95 "javascript" : "application/javascript",
96 };
97
98 OutputArea.display_order = [
99 'application/javascript',
100 'text/html',
101 'text/latex',
102 'image/svg+xml',
103 'image/png',
104 'image/jpeg',
105 'text/plain'
106 ];
107
108 OutputArea.append_map = {
109 "text/plain" : OutputArea.prototype.append_text,
110 "text/html" : OutputArea.prototype.append_html,
111 "image/svg+xml" : OutputArea.prototype.append_svg,
112 "image/png" : OutputArea.prototype.append_png,
113 "image/jpeg" : OutputArea.prototype.append_jpeg,
114 "text/latex" : OutputArea.prototype.append_latex,
115 "application/json" : OutputArea.prototype.append_json,
116 "application/javascript" : OutputArea.prototype.append_javascript,
117 };
118 45
119 46 /**
120 47 * Class prototypes
121 48 **/
122 49
123 50 OutputArea.prototype.create_elements = function () {
124 51 this.element = $("<div/>");
125 52 this.collapse_button = $("<div/>");
126 53 this.prompt_overlay = $("<div/>");
127 54 this.wrapper.append(this.prompt_overlay);
128 55 this.wrapper.append(this.element);
129 56 this.wrapper.append(this.collapse_button);
130 57 };
131 58
132 59
133 60 OutputArea.prototype.style = function () {
134 61 this.collapse_button.hide();
135 62 this.prompt_overlay.hide();
136 63
137 64 this.wrapper.addClass('output_wrapper');
138 65 this.element.addClass('output');
139 66
140 67 this.collapse_button.addClass("btn output_collapsed");
141 68 this.collapse_button.attr('title', 'click to expand output');
142 69 this.collapse_button.text('. . .');
143 70
144 71 this.prompt_overlay.addClass('out_prompt_overlay prompt');
145 72 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
146 73
147 74 this.collapse();
148 75 };
149 76
150 77 /**
151 78 * Should the OutputArea scroll?
152 79 * Returns whether the height (in lines) exceeds a threshold.
153 80 *
154 81 * @private
155 82 * @method _should_scroll
156 83 * @param [lines=100]{Integer}
157 84 * @return {Bool}
158 85 *
159 86 */
160 87 OutputArea.prototype._should_scroll = function (lines) {
161 88 if (lines <=0 ){ return }
162 89 if (!lines) {
163 90 lines = 100;
164 91 }
165 92 // line-height from http://stackoverflow.com/questions/1185151
166 93 var fontSize = this.element.css('font-size');
167 94 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
168 95
169 96 return (this.element.height() > lines * lineHeight);
170 97 };
171 98
172 99
173 100 OutputArea.prototype.bind_events = function () {
174 101 var that = this;
175 102 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
176 103 this.prompt_overlay.click(function () { that.toggle_scroll(); });
177 104
178 105 this.element.resize(function () {
179 106 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
180 107 if ( IPython.utils.browser[0] === "Firefox" ) {
181 108 return;
182 109 }
183 110 // maybe scroll output,
184 111 // if it's grown large enough and hasn't already been scrolled.
185 112 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
186 113 that.scroll_area();
187 114 }
188 115 });
189 116 this.collapse_button.click(function () {
190 117 that.expand();
191 118 });
192 119 };
193 120
194 121
195 122 OutputArea.prototype.collapse = function () {
196 123 if (!this.collapsed) {
197 124 this.element.hide();
198 125 this.prompt_overlay.hide();
199 126 if (this.element.html()){
200 127 this.collapse_button.show();
201 128 }
202 129 this.collapsed = true;
203 130 }
204 131 };
205 132
206 133
207 134 OutputArea.prototype.expand = function () {
208 135 if (this.collapsed) {
209 136 this.collapse_button.hide();
210 137 this.element.show();
211 138 this.prompt_overlay.show();
212 139 this.collapsed = false;
213 140 }
214 141 };
215 142
216 143
217 144 OutputArea.prototype.toggle_output = function () {
218 145 if (this.collapsed) {
219 146 this.expand();
220 147 } else {
221 148 this.collapse();
222 149 }
223 150 };
224 151
225 152
226 153 OutputArea.prototype.scroll_area = function () {
227 154 this.element.addClass('output_scroll');
228 155 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
229 156 this.scrolled = true;
230 157 };
231 158
232 159
233 160 OutputArea.prototype.unscroll_area = function () {
234 161 this.element.removeClass('output_scroll');
235 162 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
236 163 this.scrolled = false;
237 164 };
238 165
239 166 /**
240 167 *
241 168 * Scroll OutputArea if height supperior than a threshold (in lines).
242 169 *
243 170 * Threshold is a maximum number of lines. If unspecified, defaults to
244 171 * OutputArea.minimum_scroll_threshold.
245 172 *
246 173 * Negative threshold will prevent the OutputArea from ever scrolling.
247 174 *
248 175 * @method scroll_if_long
249 176 *
250 177 * @param [lines=20]{Number} Default to 20 if not set,
251 178 * behavior undefined for value of `0`.
252 179 *
253 180 **/
254 181 OutputArea.prototype.scroll_if_long = function (lines) {
255 182 var n = lines | OutputArea.minimum_scroll_threshold;
256 183 if(n <= 0){
257 184 return
258 185 }
259 186
260 187 if (this._should_scroll(n)) {
261 188 // only allow scrolling long-enough output
262 189 this.scroll_area();
263 190 }
264 191 };
265 192
266 193
267 194 OutputArea.prototype.toggle_scroll = function () {
268 195 if (this.scrolled) {
269 196 this.unscroll_area();
270 197 } else {
271 198 // only allow scrolling long-enough output
272 199 this.scroll_if_long();
273 200 }
274 201 };
275 202
276 203
277 204 // typeset with MathJax if MathJax is available
278 205 OutputArea.prototype.typeset = function () {
279 206 if (window.MathJax){
280 207 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
281 208 }
282 209 };
283 210
284 211
285 212 OutputArea.prototype.handle_output = function (msg) {
286 213 var json = {};
287 214 var msg_type = json.output_type = msg.header.msg_type;
288 215 var content = msg.content;
289 216 if (msg_type === "stream") {
290 217 json.text = content.data;
291 218 json.stream = content.name;
292 219 } else if (msg_type === "display_data") {
293 220 json = content.data;
294 221 json.output_type = msg_type;
295 222 json.metadata = content.metadata;
296 223 } else if (msg_type === "pyout") {
297 224 json = content.data;
298 225 json.output_type = msg_type;
299 226 json.metadata = content.metadata;
300 227 json.prompt_number = content.execution_count;
301 228 } else if (msg_type === "pyerr") {
302 229 json.ename = content.ename;
303 230 json.evalue = content.evalue;
304 231 json.traceback = content.traceback;
305 232 }
306 233 this.append_output(json);
307 234 };
308 235
309 236
310 237 OutputArea.prototype.rename_keys = function (data, key_map) {
311 238 var remapped = {};
312 239 for (var key in data) {
313 240 var new_key = key_map[key] || key;
314 241 remapped[new_key] = data[key];
315 242 }
316 243 return remapped;
317 244 };
318 245
319 246
320 247 OutputArea.output_types = [
321 248 'application/javascript',
322 249 'text/html',
323 250 'text/latex',
324 251 'image/svg+xml',
325 252 'image/png',
326 253 'image/jpeg',
327 254 'text/plain'
328 255 ];
329 256
330 257 OutputArea.prototype.validate_output = function (json) {
331 258 // scrub invalid outputs
332 259 // TODO: right now everything is a string, but JSON really shouldn't be.
333 260 // nbformat 4 will fix that.
334 261 $.map(OutputArea.output_types, function(key){
335 262 if (json[key] !== undefined && typeof json[key] !== 'string') {
336 263 console.log("Invalid type for " + key, json[key]);
337 264 delete json[key];
338 265 }
339 266 });
340 267 return json;
341 268 };
342 269
343 270 OutputArea.prototype.append_output = function (json) {
344 271 this.expand();
345 272 // Clear the output if clear is queued.
346 273 var needs_height_reset = false;
347 274 if (this.clear_queued) {
348 275 this.clear_output(false);
349 276 needs_height_reset = true;
350 277 }
351 278
352 279 // validate output data types
353 280 json = this.validate_output(json);
354 281
355 282 if (json.output_type === 'pyout') {
356 283 this.append_pyout(json);
357 284 } else if (json.output_type === 'pyerr') {
358 285 this.append_pyerr(json);
359 286 } else if (json.output_type === 'display_data') {
360 287 this.append_display_data(json);
361 288 } else if (json.output_type === 'stream') {
362 289 this.append_stream(json);
363 290 }
364 291 this.outputs.push(json);
365 292
366 293 // Only reset the height to automatic if the height is currently
367 294 // fixed (done by wait=True flag on clear_output).
368 295 if (needs_height_reset) {
369 296 this.element.height('');
370 297 }
371 298
372 299 var that = this;
373 300 setTimeout(function(){that.element.trigger('resize');}, 100);
374 301 };
375 302
376 303
377 304 OutputArea.prototype.create_output_area = function () {
378 305 var oa = $("<div/>").addClass("output_area");
379 306 if (this.prompt_area) {
380 307 oa.append($('<div/>').addClass('prompt'));
381 308 }
382 309 return oa;
383 310 };
384 311
385 312
386 313 function _get_metadata_key(metadata, key, mime) {
387 314 var mime_md = metadata[mime];
388 315 // mime-specific higher priority
389 316 if (mime_md && mime_md[key] !== undefined) {
390 317 return mime_md[key];
391 318 }
392 319 // fallback on global
393 320 return metadata[key];
394 321 }
395 322
396 323 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
397 324 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
398 325 if (_get_metadata_key(md, 'isolated', mime)) {
399 326 // Create an iframe to isolate the subarea from the rest of the
400 327 // document
401 328 var iframe = $('<iframe/>').addClass('box-flex1');
402 329 iframe.css({'height':1, 'width':'100%', 'display':'block'});
403 330 iframe.attr('frameborder', 0);
404 331 iframe.attr('scrolling', 'auto');
405 332
406 333 // Once the iframe is loaded, the subarea is dynamically inserted
407 334 iframe.on('load', function() {
408 335 // Workaround needed by Firefox, to properly render svg inside
409 336 // iframes, see http://stackoverflow.com/questions/10177190/
410 337 // svg-dynamically-added-to-iframe-does-not-render-correctly
411 338 this.contentDocument.open();
412 339
413 340 // Insert the subarea into the iframe
414 341 // We must directly write the html. When using Jquery's append
415 342 // method, javascript is evaluated in the parent document and
416 343 // not in the iframe document.
417 344 this.contentDocument.write(subarea.html());
418 345
419 346 this.contentDocument.close();
420 347
421 348 var body = this.contentDocument.body;
422 349 // Adjust the iframe height automatically
423 350 iframe.height(body.scrollHeight + 'px');
424 351 });
425 352
426 353 // Elements should be appended to the inner subarea and not to the
427 354 // iframe
428 355 iframe.append = function(that) {
429 356 subarea.append(that);
430 357 };
431 358
432 359 return iframe;
433 360 } else {
434 361 return subarea;
435 362 }
436 363 }
437 364
438 365
439 366 OutputArea.prototype._append_javascript_error = function (err, element) {
440 367 // display a message when a javascript error occurs in display output
441 368 var msg = "Javascript error adding output!"
442 369 if ( element === undefined ) return;
443 370 element.append(
444 371 $('<div/>').html(msg + "<br/>" +
445 372 err.toString() +
446 373 '<br/>See your browser Javascript console for more details.'
447 374 ).addClass('js-error')
448 375 );
449 376 };
450 377
451 378 OutputArea.prototype._safe_append = function (toinsert) {
452 379 // safely append an item to the document
453 380 // this is an object created by user code,
454 381 // and may have errors, which should not be raised
455 382 // under any circumstances.
456 383 try {
457 384 this.element.append(toinsert);
458 385 } catch(err) {
459 386 console.log(err);
460 387 // Create an actual output_area and output_subarea, which creates
461 388 // the prompt area and the proper indentation.
462 389 var toinsert = this.create_output_area();
463 390 var subarea = $('<div/>').addClass('output_subarea');
464 391 toinsert.append(subarea);
465 392 this._append_javascript_error(err, subarea);
466 393 this.element.append(toinsert);
467 394 }
468 395 };
469 396
470 397
471 398 OutputArea.prototype.append_pyout = function (json) {
472 399 var n = json.prompt_number || ' ';
473 400 var toinsert = this.create_output_area();
474 401 if (this.prompt_area) {
475 402 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
476 403 }
477 404 this.append_mime_type(json, toinsert);
478 405 this._safe_append(toinsert);
479 406 // If we just output latex, typeset it.
480 407 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
481 408 this.typeset();
482 409 }
483 410 };
484 411
485 412
486 413 OutputArea.prototype.append_pyerr = function (json) {
487 414 var tb = json.traceback;
488 415 if (tb !== undefined && tb.length > 0) {
489 416 var s = '';
490 417 var len = tb.length;
491 418 for (var i=0; i<len; i++) {
492 419 s = s + tb[i] + '\n';
493 420 }
494 421 s = s + '\n';
495 422 var toinsert = this.create_output_area();
496 423 this.append_text(s, {}, toinsert);
497 424 this._safe_append(toinsert);
498 425 }
499 426 };
500 427
501 428
502 429 OutputArea.prototype.append_stream = function (json) {
503 430 // temporary fix: if stream undefined (json file written prior to this patch),
504 431 // default to most likely stdout:
505 432 if (json.stream == undefined){
506 433 json.stream = 'stdout';
507 434 }
508 435 var text = json.text;
509 436 var subclass = "output_"+json.stream;
510 437 if (this.outputs.length > 0){
511 438 // have at least one output to consider
512 439 var last = this.outputs[this.outputs.length-1];
513 440 if (last.output_type == 'stream' && json.stream == last.stream){
514 441 // latest output was in the same stream,
515 442 // so append directly into its pre tag
516 443 // escape ANSI & HTML specials:
517 444 var pre = this.element.find('div.'+subclass).last().find('pre');
518 445 var html = utils.fixCarriageReturn(
519 446 pre.html() + utils.fixConsole(text));
520 447 pre.html(html);
521 448 return;
522 449 }
523 450 }
524 451
525 452 if (!text.replace("\r", "")) {
526 453 // text is nothing (empty string, \r, etc.)
527 454 // so don't append any elements, which might add undesirable space
528 455 return;
529 456 }
530 457
531 458 // If we got here, attach a new div
532 459 var toinsert = this.create_output_area();
533 460 this.append_text(text, {}, toinsert, "output_stream "+subclass);
534 461 this._safe_append(toinsert);
535 462 };
536 463
537 464
538 465 OutputArea.prototype.append_display_data = function (json) {
539 466 var toinsert = this.create_output_area();
540 467 if (this.append_mime_type(json, toinsert)) {
541 468 this._safe_append(toinsert);
542 469 // If we just output latex, typeset it.
543 470 if ((json['text/latex'] !== undefined) || (json['text/html'] !== undefined)) {
544 471 this.typeset();
545 472 }
546 473 }
547 474 };
548 475
549 476
550 477 OutputArea.prototype.append_mime_type = function (json, element) {
551 478
552 479 for (var type_i in OutputArea.display_order) {
553 480 var type = OutputArea.display_order[type_i];
554 481 var append = OutputArea.append_map[type];
555 482 if ((json[type] !== undefined) && append) {
556 483 var md = json.metadata || {};
557 484 var toinsert = append.apply(this, [json[type], md, element]);
558 485 $([IPython.events]).trigger('output_appended.OutputArea', [type, json[type], md, toinsert]);
559 486 return true;
560 487 }
561 488 }
562 489 return false;
563 490 };
564 491
565 492
566 493 OutputArea.prototype.append_html = function (html, md, element) {
567 494 var type = 'text/html';
568 495 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
569 496 IPython.keyboard_manager.register_events(toinsert);
570 497 toinsert.append(html);
571 498 element.append(toinsert);
572 499 return toinsert;
573 500 };
574 501
575 502
576 503 OutputArea.prototype.append_javascript = function (js, md, element) {
577 504 // We just eval the JS code, element appears in the local scope.
578 505 var type = 'application/javascript';
579 506 var toinsert = this.create_output_subarea(md, "output_javascript", type);
580 507 IPython.keyboard_manager.register_events(toinsert);
581 508 element.append(toinsert);
582 509 // FIXME TODO : remove `container element for 3.0`
583 510 //backward compat, js should be eval'ed in a context where `container` is defined.
584 511 var container = element;
585 512 container.show = function(){console.log('Warning "container.show()" is deprecated.')};
586 513 // end backward compat
587 514 try {
588 515 eval(js);
589 516 } catch(err) {
590 517 console.log(err);
591 518 this._append_javascript_error(err, toinsert);
592 519 }
593 520 return toinsert;
594 521 };
595 522
596 523
597 524 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
598 525 var type = 'text/plain';
599 526 var toinsert = this.create_output_subarea(md, "output_text", type);
600 527 // escape ANSI & HTML specials in plaintext:
601 528 data = utils.fixConsole(data);
602 529 data = utils.fixCarriageReturn(data);
603 530 data = utils.autoLinkUrls(data);
604 531 if (extra_class){
605 532 toinsert.addClass(extra_class);
606 533 }
607 534 toinsert.append($("<pre/>").html(data));
608 535 element.append(toinsert);
609 536 return toinsert;
610 537 };
611 538
612 539
613 540 OutputArea.prototype.append_svg = function (svg, md, element) {
614 541 var type = 'image/svg+xml';
615 542 var toinsert = this.create_output_subarea(md, "output_svg", type);
616 543 toinsert.append(svg);
617 544 element.append(toinsert);
618 545 return toinsert;
619 546 };
620 547
621 548
622 549 OutputArea.prototype._dblclick_to_reset_size = function (img) {
623 550 // schedule wrapping image in resizable after a delay,
624 551 // so we don't end up calling resize on a zero-size object
625 552 var that = this;
626 553 setTimeout(function () {
627 554 var h0 = img.height();
628 555 var w0 = img.width();
629 556 if (!(h0 && w0)) {
630 557 // zero size, schedule another timeout
631 558 that._dblclick_to_reset_size(img);
632 559 return;
633 560 }
634 561 img.resizable({
635 562 aspectRatio: true,
636 563 autoHide: true
637 564 });
638 565 img.dblclick(function () {
639 566 // resize wrapper & image together for some reason:
640 567 img.parent().height(h0);
641 568 img.height(h0);
642 569 img.parent().width(w0);
643 570 img.width(w0);
644 571 });
645 572 }, 250);
646 573 };
647 574
648 575
649 576 OutputArea.prototype.append_png = function (png, md, element) {
650 577 var type = 'image/png';
651 578 var toinsert = this.create_output_subarea(md, "output_png", type);
652 579 var img = $("<img/>");
653 580 img[0].setAttribute('src','data:image/png;base64,'+png);
654 581 if (md['height']) {
655 582 img[0].setAttribute('height', md['height']);
656 583 }
657 584 if (md['width']) {
658 585 img[0].setAttribute('width', md['width']);
659 586 }
660 587 this._dblclick_to_reset_size(img);
661 588 toinsert.append(img);
662 589 element.append(toinsert);
663 590 return toinsert;
664 591 };
665 592
666 593
667 594 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
668 595 var type = 'image/jpeg';
669 596 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
670 597 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
671 598 if (md['height']) {
672 599 img.attr('height', md['height']);
673 600 }
674 601 if (md['width']) {
675 602 img.attr('width', md['width']);
676 603 }
677 604 this._dblclick_to_reset_size(img);
678 605 toinsert.append(img);
679 606 element.append(toinsert);
680 607 return toinsert;
681 608 };
682 609
683 610
684 611 OutputArea.prototype.append_latex = function (latex, md, element) {
685 612 // This method cannot do the typesetting because the latex first has to
686 613 // be on the page.
687 614 var type = 'text/latex';
688 615 var toinsert = this.create_output_subarea(md, "output_latex", type);
689 616 toinsert.append(latex);
690 617 element.append(toinsert);
691 618 return toinsert;
692 619 };
693 620
694 621
695 622 OutputArea.prototype.append_raw_input = function (msg) {
696 623 var that = this;
697 624 this.expand();
698 625 var content = msg.content;
699 626 var area = this.create_output_area();
700 627
701 628 // disable any other raw_inputs, if they are left around
702 629 $("div.output_subarea.raw_input").remove();
703 630
704 631 area.append(
705 632 $("<div/>")
706 633 .addClass("box-flex1 output_subarea raw_input")
707 634 .append(
708 635 $("<span/>")
709 636 .addClass("input_prompt")
710 637 .text(content.prompt)
711 638 )
712 639 .append(
713 640 $("<input/>")
714 641 .addClass("raw_input")
715 642 .attr('type', 'text')
716 643 .attr("size", 47)
717 644 .keydown(function (event, ui) {
718 645 // make sure we submit on enter,
719 646 // and don't re-execute the *cell* on shift-enter
720 647 if (event.which === utils.keycodes.ENTER) {
721 648 that._submit_raw_input();
722 649 return false;
723 650 }
724 651 })
725 652 )
726 653 );
727 654
728 655 this.element.append(area);
729 656 var raw_input = area.find('input.raw_input');
730 657 // Register events that enable/disable the keyboard manager while raw
731 658 // input is focused.
732 659 IPython.keyboard_manager.register_events(raw_input);
733 660 // Note, the following line used to read raw_input.focus().focus().
734 661 // This seemed to be needed otherwise only the cell would be focused.
735 662 // But with the modal UI, this seems to work fine with one call to focus().
736 663 raw_input.focus();
737 664 }
738 665
739 666 OutputArea.prototype._submit_raw_input = function (evt) {
740 667 var container = this.element.find("div.raw_input");
741 668 var theprompt = container.find("span.input_prompt");
742 669 var theinput = container.find("input.raw_input");
743 670 var value = theinput.val();
744 671 var content = {
745 672 output_type : 'stream',
746 673 name : 'stdout',
747 674 text : theprompt.text() + value + '\n'
748 675 }
749 676 // remove form container
750 677 container.parent().remove();
751 678 // replace with plaintext version in stdout
752 679 this.append_output(content, false);
753 680 $([IPython.events]).trigger('send_input_reply.Kernel', value);
754 681 }
755 682
756 683
757 684 OutputArea.prototype.handle_clear_output = function (msg) {
758 685 this.clear_output(msg.content.wait);
759 686 };
760 687
761 688
762 689 OutputArea.prototype.clear_output = function(wait) {
763 690 if (wait) {
764 691
765 692 // If a clear is queued, clear before adding another to the queue.
766 693 if (this.clear_queued) {
767 694 this.clear_output(false);
768 695 };
769 696
770 697 this.clear_queued = true;
771 698 } else {
772 699
773 700 // Fix the output div's height if the clear_output is waiting for
774 701 // new output (it is being used in an animation).
775 702 if (this.clear_queued) {
776 703 var height = this.element.height();
777 704 this.element.height(height);
778 705 this.clear_queued = false;
779 706 }
780 707
781 708 // clear all, no need for logic
782 709 this.element.html("");
783 710 this.outputs = [];
784 711 this.unscroll_area();
785 712 return;
786 713 };
787 714 };
788 715
789 716
790 717 // JSON serialization
791 718
792 719 OutputArea.prototype.fromJSON = function (outputs) {
793 720 var len = outputs.length;
794 721 var data;
795 722
796 723 // We don't want to display javascript on load, so remove it from the
797 724 // display order for the duration of this function call, but be sure to
798 725 // put it back in there so incoming messages that contain javascript
799 726 // representations get displayed
800 727 var js_index = OutputArea.display_order.indexOf('application/javascript');
801 728 OutputArea.display_order.splice(js_index, 1);
802 729
803 730 for (var i=0; i<len; i++) {
804 731 data = outputs[i];
805 732 var msg_type = data.output_type;
806 733 if (msg_type === "display_data" || msg_type === "pyout") {
807 734 // convert short keys to mime keys
808 735 // TODO: remove mapping of short keys when we update to nbformat 4
809 736 data = this.rename_keys(data, OutputArea.mime_map_r);
810 737 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
811 738 }
812 739
813 740 this.append_output(data);
814 741 }
815 742
816 743 // reinsert javascript into display order, see note above
817 744 OutputArea.display_order.splice(js_index, 0, 'application/javascript');
818 745 };
819 746
820 747
821 748 OutputArea.prototype.toJSON = function () {
822 749 var outputs = [];
823 750 var len = this.outputs.length;
824 751 var data;
825 752 for (var i=0; i<len; i++) {
826 753 data = this.outputs[i];
827 754 var msg_type = data.output_type;
828 755 if (msg_type === "display_data" || msg_type === "pyout") {
829 756 // convert mime keys to short keys
830 757 data = this.rename_keys(data, OutputArea.mime_map);
831 758 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
832 759 }
833 760 outputs[i] = data;
834 761 }
835 762 return outputs;
836 763 };
837 764
765 /**
766 * Class properties
767 **/
768
769 /**
770 * Threshold to trigger autoscroll when the OutputArea is resized,
771 * typically when new outputs are added.
772 *
773 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
774 * unless it is < 0, in which case autoscroll will never be triggered
775 *
776 * @property auto_scroll_threshold
777 * @type Number
778 * @default 100
779 *
780 **/
781 OutputArea.auto_scroll_threshold = 100;
782
783 /**
784 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
785 * shorter than this are never scrolled.
786 *
787 * @property minimum_scroll_threshold
788 * @type Number
789 * @default 20
790 *
791 **/
792 OutputArea.minimum_scroll_threshold = 20;
793
794
795
796 OutputArea.mime_map = {
797 "text/plain" : "text",
798 "text/html" : "html",
799 "image/svg+xml" : "svg",
800 "image/png" : "png",
801 "image/jpeg" : "jpeg",
802 "text/latex" : "latex",
803 "application/json" : "json",
804 "application/javascript" : "javascript",
805 };
806
807 OutputArea.mime_map_r = {
808 "text" : "text/plain",
809 "html" : "text/html",
810 "svg" : "image/svg+xml",
811 "png" : "image/png",
812 "jpeg" : "image/jpeg",
813 "latex" : "text/latex",
814 "json" : "application/json",
815 "javascript" : "application/javascript",
816 };
817
818 OutputArea.display_order = [
819 'application/javascript',
820 'text/html',
821 'text/latex',
822 'image/svg+xml',
823 'image/png',
824 'image/jpeg',
825 'text/plain'
826 ];
827
828 OutputArea.append_map = {
829 "text/plain" : OutputArea.prototype.append_text,
830 "text/html" : OutputArea.prototype.append_html,
831 "image/svg+xml" : OutputArea.prototype.append_svg,
832 "image/png" : OutputArea.prototype.append_png,
833 "image/jpeg" : OutputArea.prototype.append_jpeg,
834 "text/latex" : OutputArea.prototype.append_latex,
835 "application/json" : OutputArea.prototype.append_json,
836 "application/javascript" : OutputArea.prototype.append_javascript,
837 };
838 838
839 839 IPython.OutputArea = OutputArea;
840 840
841 841 return IPython;
842 842
843 843 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now