##// END OF EJS Templates
Finished changing output widget logic.
Jonathan Frederic -
Show More
@@ -1,644 +1,645 b''
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 *
5 *
6 * @module codecell
6 * @module codecell
7 * @namespace codecell
7 * @namespace codecell
8 * @class CodeCell
8 * @class CodeCell
9 */
9 */
10
10
11
11
12 define([
12 define([
13 'base/js/namespace',
13 'base/js/namespace',
14 'jquery',
14 'jquery',
15 'base/js/utils',
15 'base/js/utils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'services/config',
17 'services/config',
18 'notebook/js/cell',
18 'notebook/js/cell',
19 'notebook/js/outputarea',
19 'notebook/js/outputarea',
20 'notebook/js/completer',
20 'notebook/js/completer',
21 'notebook/js/celltoolbar',
21 'notebook/js/celltoolbar',
22 'codemirror/lib/codemirror',
22 'codemirror/lib/codemirror',
23 'codemirror/mode/python/python',
23 'codemirror/mode/python/python',
24 'notebook/js/codemirror-ipython'
24 'notebook/js/codemirror-ipython'
25 ], function(IPython,
25 ], function(IPython,
26 $,
26 $,
27 utils,
27 utils,
28 keyboard,
28 keyboard,
29 configmod,
29 configmod,
30 cell,
30 cell,
31 outputarea,
31 outputarea,
32 completer,
32 completer,
33 celltoolbar,
33 celltoolbar,
34 CodeMirror,
34 CodeMirror,
35 cmpython,
35 cmpython,
36 cmip
36 cmip
37 ) {
37 ) {
38 "use strict";
38 "use strict";
39
39
40 var Cell = cell.Cell;
40 var Cell = cell.Cell;
41
41
42 /* local util for codemirror */
42 /* local util for codemirror */
43 var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
43 var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
44
44
45 /**
45 /**
46 *
46 *
47 * function to delete until previous non blanking space character
47 * function to delete until previous non blanking space character
48 * or first multiple of 4 tabstop.
48 * or first multiple of 4 tabstop.
49 * @private
49 * @private
50 */
50 */
51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
55 var tabsize = cm.getOption('tabSize');
55 var tabsize = cm.getOption('tabSize');
56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
58 var select = cm.getRange(from,cur);
58 var select = cm.getRange(from,cur);
59 if( select.match(/^\ +$/) !== null){
59 if( select.match(/^\ +$/) !== null){
60 cm.replaceRange("",from,cur);
60 cm.replaceRange("",from,cur);
61 } else {
61 } else {
62 cm.deleteH(-1,"char");
62 cm.deleteH(-1,"char");
63 }
63 }
64 };
64 };
65
65
66 var keycodes = keyboard.keycodes;
66 var keycodes = keyboard.keycodes;
67
67
68 var CodeCell = function (kernel, options) {
68 var CodeCell = function (kernel, options) {
69 /**
69 /**
70 * Constructor
70 * Constructor
71 *
71 *
72 * A Cell conceived to write code.
72 * A Cell conceived to write code.
73 *
73 *
74 * Parameters:
74 * Parameters:
75 * kernel: Kernel instance
75 * kernel: Kernel instance
76 * The kernel doesn't have to be set at creation time, in that case
76 * The kernel doesn't have to be set at creation time, in that case
77 * it will be null and set_kernel has to be called later.
77 * it will be null and set_kernel has to be called later.
78 * options: dictionary
78 * options: dictionary
79 * Dictionary of keyword arguments.
79 * Dictionary of keyword arguments.
80 * events: $(Events) instance
80 * events: $(Events) instance
81 * config: dictionary
81 * config: dictionary
82 * keyboard_manager: KeyboardManager instance
82 * keyboard_manager: KeyboardManager instance
83 * notebook: Notebook instance
83 * notebook: Notebook instance
84 * tooltip: Tooltip instance
84 * tooltip: Tooltip instance
85 */
85 */
86 this.kernel = kernel || null;
86 this.kernel = kernel || null;
87 this.notebook = options.notebook;
87 this.notebook = options.notebook;
88 this.collapsed = false;
88 this.collapsed = false;
89 this.events = options.events;
89 this.events = options.events;
90 this.tooltip = options.tooltip;
90 this.tooltip = options.tooltip;
91 this.config = options.config;
91 this.config = options.config;
92 this.class_config = new configmod.ConfigWithDefaults(this.config,
92 this.class_config = new configmod.ConfigWithDefaults(this.config,
93 CodeCell.config_defaults, 'CodeCell');
93 CodeCell.config_defaults, 'CodeCell');
94
94
95 // create all attributed in constructor function
95 // create all attributed in constructor function
96 // even if null for V8 VM optimisation
96 // even if null for V8 VM optimisation
97 this.input_prompt_number = null;
97 this.input_prompt_number = null;
98 this.celltoolbar = null;
98 this.celltoolbar = null;
99 this.output_area = null;
99 this.output_area = null;
100
100
101 this.last_msg_id = null;
101 this.last_msg_id = null;
102 this.completer = null;
102 this.completer = null;
103 this.widget_views = [];
103 this.widget_views = [];
104 this._widgets_live = true;
104 this._widgets_live = true;
105
105
106 Cell.apply(this,[{
106 Cell.apply(this,[{
107 config: $.extend({}, CodeCell.options_default),
107 config: $.extend({}, CodeCell.options_default),
108 keyboard_manager: options.keyboard_manager,
108 keyboard_manager: options.keyboard_manager,
109 events: this.events}]);
109 events: this.events}]);
110
110
111 // Attributes we want to override in this subclass.
111 // Attributes we want to override in this subclass.
112 this.cell_type = "code";
112 this.cell_type = "code";
113 var that = this;
113 this.element.focusout(
114 this.element.focusout(
114 function() { that.auto_highlight(); }
115 function() { that.auto_highlight(); }
115 );
116 );
116 };
117 };
117
118
118 CodeCell.options_default = {
119 CodeCell.options_default = {
119 cm_config : {
120 cm_config : {
120 extraKeys: {
121 extraKeys: {
121 "Tab" : "indentMore",
122 "Tab" : "indentMore",
122 "Shift-Tab" : "indentLess",
123 "Shift-Tab" : "indentLess",
123 "Backspace" : "delSpaceToPrevTabStop",
124 "Backspace" : "delSpaceToPrevTabStop",
124 "Cmd-/" : "toggleComment",
125 "Cmd-/" : "toggleComment",
125 "Ctrl-/" : "toggleComment"
126 "Ctrl-/" : "toggleComment"
126 },
127 },
127 mode: 'ipython',
128 mode: 'ipython',
128 theme: 'ipython',
129 theme: 'ipython',
129 matchBrackets: true
130 matchBrackets: true
130 }
131 }
131 };
132 };
132
133
133 CodeCell.config_defaults = {
134 CodeCell.config_defaults = {
134 highlight_modes : {
135 highlight_modes : {
135 'magic_javascript' :{'reg':[/^%%javascript/]},
136 'magic_javascript' :{'reg':[/^%%javascript/]},
136 'magic_perl' :{'reg':[/^%%perl/]},
137 'magic_perl' :{'reg':[/^%%perl/]},
137 'magic_ruby' :{'reg':[/^%%ruby/]},
138 'magic_ruby' :{'reg':[/^%%ruby/]},
138 'magic_python' :{'reg':[/^%%python3?/]},
139 'magic_python' :{'reg':[/^%%python3?/]},
139 'magic_shell' :{'reg':[/^%%bash/]},
140 'magic_shell' :{'reg':[/^%%bash/]},
140 'magic_r' :{'reg':[/^%%R/]},
141 'magic_r' :{'reg':[/^%%R/]},
141 'magic_text/x-cython' :{'reg':[/^%%cython/]},
142 'magic_text/x-cython' :{'reg':[/^%%cython/]},
142 },
143 },
143 };
144 };
144
145
145 CodeCell.msg_cells = {};
146 CodeCell.msg_cells = {};
146
147
147 CodeCell.prototype = Object.create(Cell.prototype);
148 CodeCell.prototype = Object.create(Cell.prototype);
148
149
149 /** @method create_element */
150 /** @method create_element */
150 CodeCell.prototype.create_element = function () {
151 CodeCell.prototype.create_element = function () {
151 Cell.prototype.create_element.apply(this, arguments);
152 Cell.prototype.create_element.apply(this, arguments);
152
153
153 var cell = $('<div></div>').addClass('cell code_cell');
154 var cell = $('<div></div>').addClass('cell code_cell');
154 cell.attr('tabindex','2');
155 cell.attr('tabindex','2');
155
156
156 var input = $('<div></div>').addClass('input');
157 var input = $('<div></div>').addClass('input');
157 var prompt = $('<div/>').addClass('prompt input_prompt');
158 var prompt = $('<div/>').addClass('prompt input_prompt');
158 var inner_cell = $('<div/>').addClass('inner_cell');
159 var inner_cell = $('<div/>').addClass('inner_cell');
159 this.celltoolbar = new celltoolbar.CellToolbar({
160 this.celltoolbar = new celltoolbar.CellToolbar({
160 cell: this,
161 cell: this,
161 notebook: this.notebook});
162 notebook: this.notebook});
162 inner_cell.append(this.celltoolbar.element);
163 inner_cell.append(this.celltoolbar.element);
163 var input_area = $('<div/>').addClass('input_area');
164 var input_area = $('<div/>').addClass('input_area');
164 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
165 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
165 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
166 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
166 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
167 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
167 inner_cell.append(input_area);
168 inner_cell.append(input_area);
168 input.append(prompt).append(inner_cell);
169 input.append(prompt).append(inner_cell);
169
170
170 var widget_area = $('<div/>')
171 var widget_area = $('<div/>')
171 .addClass('widget-area')
172 .addClass('widget-area')
172 .hide();
173 .hide();
173 this.widget_area = widget_area;
174 this.widget_area = widget_area;
174 var widget_prompt = $('<div/>')
175 var widget_prompt = $('<div/>')
175 .addClass('prompt')
176 .addClass('prompt')
176 .appendTo(widget_area);
177 .appendTo(widget_area);
177 var widget_subarea = $('<div/>')
178 var widget_subarea = $('<div/>')
178 .addClass('widget-subarea')
179 .addClass('widget-subarea')
179 .appendTo(widget_area);
180 .appendTo(widget_area);
180 this.widget_subarea = widget_subarea;
181 this.widget_subarea = widget_subarea;
181 var that = this;
182 var that = this;
182 var widget_clear_buton = $('<button />')
183 var widget_clear_buton = $('<button />')
183 .addClass('close')
184 .addClass('close')
184 .html('&times;')
185 .html('&times;')
185 .click(function() {
186 .click(function() {
186 widget_area.slideUp('', function(){
187 widget_area.slideUp('', function(){
187 for (var i = 0; i < that.widget_views.length; i++) {
188 for (var i = 0; i < that.widget_views.length; i++) {
188 var view = that.widget_views[i];
189 var view = that.widget_views[i];
189 view.remove();
190 view.remove();
190
191
191 // Remove widget live events.
192 // Remove widget live events.
192 view.off('comm:live', that._widget_live);
193 view.off('comm:live', that._widget_live);
193 view.off('comm:dead', that._widget_dead);
194 view.off('comm:dead', that._widget_dead);
194 }
195 }
195 that.widget_views = [];
196 that.widget_views = [];
196 widget_subarea.html('');
197 widget_subarea.html('');
197 });
198 });
198 })
199 })
199 .appendTo(widget_prompt);
200 .appendTo(widget_prompt);
200
201
201 var output = $('<div></div>');
202 var output = $('<div></div>');
202 cell.append(input).append(widget_area).append(output);
203 cell.append(input).append(widget_area).append(output);
203 this.element = cell;
204 this.element = cell;
204 this.output_area = new outputarea.OutputArea({
205 this.output_area = new outputarea.OutputArea({
205 selector: output,
206 selector: output,
206 prompt_area: true,
207 prompt_area: true,
207 events: this.events,
208 events: this.events,
208 keyboard_manager: this.keyboard_manager});
209 keyboard_manager: this.keyboard_manager});
209 this.completer = new completer.Completer(this, this.events);
210 this.completer = new completer.Completer(this, this.events);
210 };
211 };
211
212
212 /**
213 /**
213 * Display a widget view in the cell.
214 * Display a widget view in the cell.
214 */
215 */
215 CodeCell.prototype.display_widget_view = function(view_promise) {
216 CodeCell.prototype.display_widget_view = function(view_promise) {
216
217
217 // Display a dummy element
218 // Display a dummy element
218 var dummy = $('<div/>');
219 var dummy = $('<div/>');
219 this.widget_subarea.append(dummy);
220 this.widget_subarea.append(dummy);
220
221
221 // Display the view.
222 // Display the view.
222 var that = this;
223 var that = this;
223 return view_promise.then(function(view) {
224 return view_promise.then(function(view) {
224 that.widget_area.show();
225 that.widget_area.show();
225 dummy.replaceWith(view.$el);
226 dummy.replaceWith(view.$el);
226 that.widget_views.push(view);
227 that.widget_views.push(view);
227
228
228 // Check the live state of the view's model.
229 // Check the live state of the view's model.
229 if (view.model.comm_live) {
230 if (view.model.comm_live) {
230 that._widget_live(view);
231 that._widget_live(view);
231 } else {
232 } else {
232 that._widget_dead(view);
233 that._widget_dead(view);
233 }
234 }
234
235
235 // Listen to comm live events for the view.
236 // Listen to comm live events for the view.
236 view.on('comm:live', that._widget_live, that);
237 view.on('comm:live', that._widget_live, that);
237 view.on('comm:dead', that._widget_dead, that);
238 view.on('comm:dead', that._widget_dead, that);
238 return view;
239 return view;
239 });
240 });
240 };
241 };
241
242
242 /**
243 /**
243 * Handles when a widget loses it's comm connection.
244 * Handles when a widget loses it's comm connection.
244 * @param {WidgetView} view
245 * @param {WidgetView} view
245 */
246 */
246 CodeCell.prototype._widget_dead = function(view) {
247 CodeCell.prototype._widget_dead = function(view) {
247 if (this._widgets_live) {
248 if (this._widgets_live) {
248 this._widgets_live = false;
249 this._widgets_live = false;
249 this.widget_area.addClass('connection-problems');
250 this.widget_area.addClass('connection-problems');
250 }
251 }
251
252
252 };
253 };
253
254
254 /**
255 /**
255 * Handles when a widget is connected to a live comm.
256 * Handles when a widget is connected to a live comm.
256 * @param {WidgetView} view
257 * @param {WidgetView} view
257 */
258 */
258 CodeCell.prototype._widget_live = function(view) {
259 CodeCell.prototype._widget_live = function(view) {
259 if (!this._widgets_live) {
260 if (!this._widgets_live) {
260 // Check that the other widgets are live too. O(N) operation.
261 // Check that the other widgets are live too. O(N) operation.
261 // Abort the function at the first dead widget found.
262 // Abort the function at the first dead widget found.
262 for (var i = 0; i < this.widget_views.length; i++) {
263 for (var i = 0; i < this.widget_views.length; i++) {
263 if (!this.widget_views[i].model.comm_live) return;
264 if (!this.widget_views[i].model.comm_live) return;
264 }
265 }
265 this._widgets_live = true;
266 this._widgets_live = true;
266 this.widget_area.removeClass('connection-problems');
267 this.widget_area.removeClass('connection-problems');
267 }
268 }
268 };
269 };
269
270
270 /** @method bind_events */
271 /** @method bind_events */
271 CodeCell.prototype.bind_events = function () {
272 CodeCell.prototype.bind_events = function () {
272 Cell.prototype.bind_events.apply(this);
273 Cell.prototype.bind_events.apply(this);
273 var that = this;
274 var that = this;
274
275
275 this.element.focusout(
276 this.element.focusout(
276 function() { that.auto_highlight(); }
277 function() { that.auto_highlight(); }
277 );
278 );
278 };
279 };
279
280
280
281
281 /**
282 /**
282 * This method gets called in CodeMirror's onKeyDown/onKeyPress
283 * This method gets called in CodeMirror's onKeyDown/onKeyPress
283 * handlers and is used to provide custom key handling. Its return
284 * handlers and is used to provide custom key handling. Its return
284 * value is used to determine if CodeMirror should ignore the event:
285 * value is used to determine if CodeMirror should ignore the event:
285 * true = ignore, false = don't ignore.
286 * true = ignore, false = don't ignore.
286 * @method handle_codemirror_keyevent
287 * @method handle_codemirror_keyevent
287 */
288 */
288
289
289 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
290 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
290
291
291 var that = this;
292 var that = this;
292 // whatever key is pressed, first, cancel the tooltip request before
293 // whatever key is pressed, first, cancel the tooltip request before
293 // they are sent, and remove tooltip if any, except for tab again
294 // they are sent, and remove tooltip if any, except for tab again
294 var tooltip_closed = null;
295 var tooltip_closed = null;
295 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
296 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
296 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
297 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
297 }
298 }
298
299
299 var cur = editor.getCursor();
300 var cur = editor.getCursor();
300 if (event.keyCode === keycodes.enter){
301 if (event.keyCode === keycodes.enter){
301 this.auto_highlight();
302 this.auto_highlight();
302 }
303 }
303
304
304 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
305 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
305 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
306 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
306 // browser and keyboard layout !
307 // browser and keyboard layout !
307 // Pressing '(' , request tooltip, don't forget to reappend it
308 // Pressing '(' , request tooltip, don't forget to reappend it
308 // The second argument says to hide the tooltip if the docstring
309 // The second argument says to hide the tooltip if the docstring
309 // is actually empty
310 // is actually empty
310 this.tooltip.pending(that, true);
311 this.tooltip.pending(that, true);
311 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
312 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
312 // If tooltip is active, cancel it. The call to
313 // If tooltip is active, cancel it. The call to
313 // remove_and_cancel_tooltip above doesn't pass, force=true.
314 // remove_and_cancel_tooltip above doesn't pass, force=true.
314 // Because of this it won't actually close the tooltip
315 // Because of this it won't actually close the tooltip
315 // if it is in sticky mode. Thus, we have to check again if it is open
316 // if it is in sticky mode. Thus, we have to check again if it is open
316 // and close it with force=true.
317 // and close it with force=true.
317 if (!this.tooltip._hidden) {
318 if (!this.tooltip._hidden) {
318 this.tooltip.remove_and_cancel_tooltip(true);
319 this.tooltip.remove_and_cancel_tooltip(true);
319 }
320 }
320 // If we closed the tooltip, don't let CM or the global handlers
321 // If we closed the tooltip, don't let CM or the global handlers
321 // handle this event.
322 // handle this event.
322 event.codemirrorIgnore = true;
323 event.codemirrorIgnore = true;
323 event.preventDefault();
324 event.preventDefault();
324 return true;
325 return true;
325 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
326 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
326 if (editor.somethingSelected() || editor.getSelections().length !== 1){
327 if (editor.somethingSelected() || editor.getSelections().length !== 1){
327 var anchor = editor.getCursor("anchor");
328 var anchor = editor.getCursor("anchor");
328 var head = editor.getCursor("head");
329 var head = editor.getCursor("head");
329 if( anchor.line !== head.line){
330 if( anchor.line !== head.line){
330 return false;
331 return false;
331 }
332 }
332 }
333 }
333 this.tooltip.request(that);
334 this.tooltip.request(that);
334 event.codemirrorIgnore = true;
335 event.codemirrorIgnore = true;
335 event.preventDefault();
336 event.preventDefault();
336 return true;
337 return true;
337 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
338 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
338 // Tab completion.
339 // Tab completion.
339 this.tooltip.remove_and_cancel_tooltip();
340 this.tooltip.remove_and_cancel_tooltip();
340
341
341 // completion does not work on multicursor, it might be possible though in some cases
342 // completion does not work on multicursor, it might be possible though in some cases
342 if (editor.somethingSelected() || editor.getSelections().length > 1) {
343 if (editor.somethingSelected() || editor.getSelections().length > 1) {
343 return false;
344 return false;
344 }
345 }
345 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
346 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
346 if (pre_cursor.trim() === "") {
347 if (pre_cursor.trim() === "") {
347 // Don't autocomplete if the part of the line before the cursor
348 // Don't autocomplete if the part of the line before the cursor
348 // is empty. In this case, let CodeMirror handle indentation.
349 // is empty. In this case, let CodeMirror handle indentation.
349 return false;
350 return false;
350 } else {
351 } else {
351 event.codemirrorIgnore = true;
352 event.codemirrorIgnore = true;
352 event.preventDefault();
353 event.preventDefault();
353 this.completer.startCompletion();
354 this.completer.startCompletion();
354 return true;
355 return true;
355 }
356 }
356 }
357 }
357
358
358 // keyboard event wasn't one of those unique to code cells, let's see
359 // keyboard event wasn't one of those unique to code cells, let's see
359 // if it's one of the generic ones (i.e. check edit mode shortcuts)
360 // if it's one of the generic ones (i.e. check edit mode shortcuts)
360 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
361 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
361 };
362 };
362
363
363 // Kernel related calls.
364 // Kernel related calls.
364
365
365 CodeCell.prototype.set_kernel = function (kernel) {
366 CodeCell.prototype.set_kernel = function (kernel) {
366 this.kernel = kernel;
367 this.kernel = kernel;
367 };
368 };
368
369
369 /**
370 /**
370 * Execute current code cell to the kernel
371 * Execute current code cell to the kernel
371 * @method execute
372 * @method execute
372 */
373 */
373 CodeCell.prototype.execute = function (stop_on_error) {
374 CodeCell.prototype.execute = function (stop_on_error) {
374 if (!this.kernel || !this.kernel.is_connected()) {
375 if (!this.kernel || !this.kernel.is_connected()) {
375 console.log("Can't execute, kernel is not connected.");
376 console.log("Can't execute, kernel is not connected.");
376 return;
377 return;
377 }
378 }
378
379
379 this.output_area.clear_output(false, true);
380 this.output_area.clear_output(false, true);
380
381
381 if (stop_on_error === undefined) {
382 if (stop_on_error === undefined) {
382 stop_on_error = true;
383 stop_on_error = true;
383 }
384 }
384
385
385 // Clear widget area
386 // Clear widget area
386 for (var i = 0; i < this.widget_views.length; i++) {
387 for (var i = 0; i < this.widget_views.length; i++) {
387 var view = this.widget_views[i];
388 var view = this.widget_views[i];
388 view.remove();
389 view.remove();
389
390
390 // Remove widget live events.
391 // Remove widget live events.
391 view.off('comm:live', this._widget_live);
392 view.off('comm:live', this._widget_live);
392 view.off('comm:dead', this._widget_dead);
393 view.off('comm:dead', this._widget_dead);
393 }
394 }
394 this.widget_views = [];
395 this.widget_views = [];
395 this.widget_subarea.html('');
396 this.widget_subarea.html('');
396 this.widget_subarea.height('');
397 this.widget_subarea.height('');
397 this.widget_area.height('');
398 this.widget_area.height('');
398 this.widget_area.hide();
399 this.widget_area.hide();
399
400
400 this.set_input_prompt('*');
401 this.set_input_prompt('*');
401 this.element.addClass("running");
402 this.element.addClass("running");
402 if (this.last_msg_id) {
403 if (this.last_msg_id) {
403 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
404 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
404 }
405 }
405 var callbacks = this.get_callbacks();
406 var callbacks = this.get_callbacks();
406
407
407 var old_msg_id = this.last_msg_id;
408 var old_msg_id = this.last_msg_id;
408 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
409 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
409 stop_on_error : stop_on_error});
410 stop_on_error : stop_on_error});
410 if (old_msg_id) {
411 if (old_msg_id) {
411 delete CodeCell.msg_cells[old_msg_id];
412 delete CodeCell.msg_cells[old_msg_id];
412 }
413 }
413 CodeCell.msg_cells[this.last_msg_id] = this;
414 CodeCell.msg_cells[this.last_msg_id] = this;
414 this.render();
415 this.render();
415 this.events.trigger('execute.CodeCell', {cell: this});
416 this.events.trigger('execute.CodeCell', {cell: this});
416 };
417 };
417
418
418 /**
419 /**
419 * Construct the default callbacks for
420 * Construct the default callbacks for
420 * @method get_callbacks
421 * @method get_callbacks
421 */
422 */
422 CodeCell.prototype.get_callbacks = function () {
423 CodeCell.prototype.get_callbacks = function () {
423 var that = this;
424 var that = this;
424 return {
425 return {
425 shell : {
426 shell : {
426 reply : $.proxy(this._handle_execute_reply, this),
427 reply : $.proxy(this._handle_execute_reply, this),
427 payload : {
428 payload : {
428 set_next_input : $.proxy(this._handle_set_next_input, this),
429 set_next_input : $.proxy(this._handle_set_next_input, this),
429 page : $.proxy(this._open_with_pager, this)
430 page : $.proxy(this._open_with_pager, this)
430 }
431 }
431 },
432 },
432 iopub : {
433 iopub : {
433 output : function() {
434 output : function() {
434 that.output_area.handle_output.apply(that.output_area, arguments);
435 that.output_area.handle_output.apply(that.output_area, arguments);
435 },
436 },
436 clear_output : function() {
437 clear_output : function() {
437 that.output_area.handle_clear_output.apply(that.output_area, arguments);
438 that.output_area.handle_clear_output.apply(that.output_area, arguments);
438 },
439 },
439 },
440 },
440 input : $.proxy(this._handle_input_request, this)
441 input : $.proxy(this._handle_input_request, this)
441 };
442 };
442 };
443 };
443
444
444 CodeCell.prototype._open_with_pager = function (payload) {
445 CodeCell.prototype._open_with_pager = function (payload) {
445 this.events.trigger('open_with_text.Pager', payload);
446 this.events.trigger('open_with_text.Pager', payload);
446 };
447 };
447
448
448 /**
449 /**
449 * @method _handle_execute_reply
450 * @method _handle_execute_reply
450 * @private
451 * @private
451 */
452 */
452 CodeCell.prototype._handle_execute_reply = function (msg) {
453 CodeCell.prototype._handle_execute_reply = function (msg) {
453 this.set_input_prompt(msg.content.execution_count);
454 this.set_input_prompt(msg.content.execution_count);
454 this.element.removeClass("running");
455 this.element.removeClass("running");
455 this.events.trigger('set_dirty.Notebook', {value: true});
456 this.events.trigger('set_dirty.Notebook', {value: true});
456 };
457 };
457
458
458 /**
459 /**
459 * @method _handle_set_next_input
460 * @method _handle_set_next_input
460 * @private
461 * @private
461 */
462 */
462 CodeCell.prototype._handle_set_next_input = function (payload) {
463 CodeCell.prototype._handle_set_next_input = function (payload) {
463 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
464 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
464 this.events.trigger('set_next_input.Notebook', data);
465 this.events.trigger('set_next_input.Notebook', data);
465 };
466 };
466
467
467 /**
468 /**
468 * @method _handle_input_request
469 * @method _handle_input_request
469 * @private
470 * @private
470 */
471 */
471 CodeCell.prototype._handle_input_request = function (msg) {
472 CodeCell.prototype._handle_input_request = function (msg) {
472 this.output_area.append_raw_input(msg);
473 this.output_area.append_raw_input(msg);
473 };
474 };
474
475
475
476
476 // Basic cell manipulation.
477 // Basic cell manipulation.
477
478
478 CodeCell.prototype.select = function () {
479 CodeCell.prototype.select = function () {
479 var cont = Cell.prototype.select.apply(this);
480 var cont = Cell.prototype.select.apply(this);
480 if (cont) {
481 if (cont) {
481 this.code_mirror.refresh();
482 this.code_mirror.refresh();
482 this.auto_highlight();
483 this.auto_highlight();
483 }
484 }
484 return cont;
485 return cont;
485 };
486 };
486
487
487 CodeCell.prototype.render = function () {
488 CodeCell.prototype.render = function () {
488 var cont = Cell.prototype.render.apply(this);
489 var cont = Cell.prototype.render.apply(this);
489 // Always execute, even if we are already in the rendered state
490 // Always execute, even if we are already in the rendered state
490 return cont;
491 return cont;
491 };
492 };
492
493
493 CodeCell.prototype.select_all = function () {
494 CodeCell.prototype.select_all = function () {
494 var start = {line: 0, ch: 0};
495 var start = {line: 0, ch: 0};
495 var nlines = this.code_mirror.lineCount();
496 var nlines = this.code_mirror.lineCount();
496 var last_line = this.code_mirror.getLine(nlines-1);
497 var last_line = this.code_mirror.getLine(nlines-1);
497 var end = {line: nlines-1, ch: last_line.length};
498 var end = {line: nlines-1, ch: last_line.length};
498 this.code_mirror.setSelection(start, end);
499 this.code_mirror.setSelection(start, end);
499 };
500 };
500
501
501
502
502 CodeCell.prototype.collapse_output = function () {
503 CodeCell.prototype.collapse_output = function () {
503 this.output_area.collapse();
504 this.output_area.collapse();
504 };
505 };
505
506
506
507
507 CodeCell.prototype.expand_output = function () {
508 CodeCell.prototype.expand_output = function () {
508 this.output_area.expand();
509 this.output_area.expand();
509 this.output_area.unscroll_area();
510 this.output_area.unscroll_area();
510 };
511 };
511
512
512 CodeCell.prototype.scroll_output = function () {
513 CodeCell.prototype.scroll_output = function () {
513 this.output_area.expand();
514 this.output_area.expand();
514 this.output_area.scroll_if_long();
515 this.output_area.scroll_if_long();
515 };
516 };
516
517
517 CodeCell.prototype.toggle_output = function () {
518 CodeCell.prototype.toggle_output = function () {
518 this.output_area.toggle_output();
519 this.output_area.toggle_output();
519 };
520 };
520
521
521 CodeCell.prototype.toggle_output_scroll = function () {
522 CodeCell.prototype.toggle_output_scroll = function () {
522 this.output_area.toggle_scroll();
523 this.output_area.toggle_scroll();
523 };
524 };
524
525
525
526
526 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
527 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
527 var ns;
528 var ns;
528 if (prompt_value === undefined || prompt_value === null) {
529 if (prompt_value === undefined || prompt_value === null) {
529 ns = "&nbsp;";
530 ns = "&nbsp;";
530 } else {
531 } else {
531 ns = encodeURIComponent(prompt_value);
532 ns = encodeURIComponent(prompt_value);
532 }
533 }
533 return 'In&nbsp;[' + ns + ']:';
534 return 'In&nbsp;[' + ns + ']:';
534 };
535 };
535
536
536 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
537 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
537 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
538 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
538 for(var i=1; i < lines_number; i++) {
539 for(var i=1; i < lines_number; i++) {
539 html.push(['...:']);
540 html.push(['...:']);
540 }
541 }
541 return html.join('<br/>');
542 return html.join('<br/>');
542 };
543 };
543
544
544 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
545 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
545
546
546
547
547 CodeCell.prototype.set_input_prompt = function (number) {
548 CodeCell.prototype.set_input_prompt = function (number) {
548 var nline = 1;
549 var nline = 1;
549 if (this.code_mirror !== undefined) {
550 if (this.code_mirror !== undefined) {
550 nline = this.code_mirror.lineCount();
551 nline = this.code_mirror.lineCount();
551 }
552 }
552 this.input_prompt_number = number;
553 this.input_prompt_number = number;
553 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
554 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
554 // This HTML call is okay because the user contents are escaped.
555 // This HTML call is okay because the user contents are escaped.
555 this.element.find('div.input_prompt').html(prompt_html);
556 this.element.find('div.input_prompt').html(prompt_html);
556 };
557 };
557
558
558
559
559 CodeCell.prototype.clear_input = function () {
560 CodeCell.prototype.clear_input = function () {
560 this.code_mirror.setValue('');
561 this.code_mirror.setValue('');
561 };
562 };
562
563
563
564
564 CodeCell.prototype.get_text = function () {
565 CodeCell.prototype.get_text = function () {
565 return this.code_mirror.getValue();
566 return this.code_mirror.getValue();
566 };
567 };
567
568
568
569
569 CodeCell.prototype.set_text = function (code) {
570 CodeCell.prototype.set_text = function (code) {
570 return this.code_mirror.setValue(code);
571 return this.code_mirror.setValue(code);
571 };
572 };
572
573
573
574
574 CodeCell.prototype.clear_output = function (wait) {
575 CodeCell.prototype.clear_output = function (wait) {
575 this.output_area.clear_output(wait);
576 this.output_area.clear_output(wait);
576 this.set_input_prompt();
577 this.set_input_prompt();
577 };
578 };
578
579
579
580
580 // JSON serialization
581 // JSON serialization
581
582
582 CodeCell.prototype.fromJSON = function (data) {
583 CodeCell.prototype.fromJSON = function (data) {
583 Cell.prototype.fromJSON.apply(this, arguments);
584 Cell.prototype.fromJSON.apply(this, arguments);
584 if (data.cell_type === 'code') {
585 if (data.cell_type === 'code') {
585 if (data.source !== undefined) {
586 if (data.source !== undefined) {
586 this.set_text(data.source);
587 this.set_text(data.source);
587 // make this value the starting point, so that we can only undo
588 // make this value the starting point, so that we can only undo
588 // to this state, instead of a blank cell
589 // to this state, instead of a blank cell
589 this.code_mirror.clearHistory();
590 this.code_mirror.clearHistory();
590 this.auto_highlight();
591 this.auto_highlight();
591 }
592 }
592 this.set_input_prompt(data.execution_count);
593 this.set_input_prompt(data.execution_count);
593 this.output_area.trusted = data.metadata.trusted || false;
594 this.output_area.trusted = data.metadata.trusted || false;
594 this.output_area.fromJSON(data.outputs);
595 this.output_area.fromJSON(data.outputs);
595 if (data.metadata.collapsed !== undefined) {
596 if (data.metadata.collapsed !== undefined) {
596 if (data.metadata.collapsed) {
597 if (data.metadata.collapsed) {
597 this.collapse_output();
598 this.collapse_output();
598 } else {
599 } else {
599 this.expand_output();
600 this.expand_output();
600 }
601 }
601 }
602 }
602 }
603 }
603 };
604 };
604
605
605
606
606 CodeCell.prototype.toJSON = function () {
607 CodeCell.prototype.toJSON = function () {
607 var data = Cell.prototype.toJSON.apply(this);
608 var data = Cell.prototype.toJSON.apply(this);
608 data.source = this.get_text();
609 data.source = this.get_text();
609 // is finite protect against undefined and '*' value
610 // is finite protect against undefined and '*' value
610 if (isFinite(this.input_prompt_number)) {
611 if (isFinite(this.input_prompt_number)) {
611 data.execution_count = this.input_prompt_number;
612 data.execution_count = this.input_prompt_number;
612 } else {
613 } else {
613 data.execution_count = null;
614 data.execution_count = null;
614 }
615 }
615 var outputs = this.output_area.toJSON();
616 var outputs = this.output_area.toJSON();
616 data.outputs = outputs;
617 data.outputs = outputs;
617 data.metadata.trusted = this.output_area.trusted;
618 data.metadata.trusted = this.output_area.trusted;
618 data.metadata.collapsed = this.output_area.collapsed;
619 data.metadata.collapsed = this.output_area.collapsed;
619 return data;
620 return data;
620 };
621 };
621
622
622 /**
623 /**
623 * handle cell level logic when a cell is unselected
624 * handle cell level logic when a cell is unselected
624 * @method unselect
625 * @method unselect
625 * @return is the action being taken
626 * @return is the action being taken
626 */
627 */
627 CodeCell.prototype.unselect = function () {
628 CodeCell.prototype.unselect = function () {
628 var cont = Cell.prototype.unselect.apply(this);
629 var cont = Cell.prototype.unselect.apply(this);
629 if (cont) {
630 if (cont) {
630 // When a code cell is usnelected, make sure that the corresponding
631 // When a code cell is usnelected, make sure that the corresponding
631 // tooltip and completer to that cell is closed.
632 // tooltip and completer to that cell is closed.
632 this.tooltip.remove_and_cancel_tooltip(true);
633 this.tooltip.remove_and_cancel_tooltip(true);
633 if (this.completer !== null) {
634 if (this.completer !== null) {
634 this.completer.close();
635 this.completer.close();
635 }
636 }
636 }
637 }
637 return cont;
638 return cont;
638 };
639 };
639
640
640 // Backwards compatability.
641 // Backwards compatability.
641 IPython.CodeCell = CodeCell;
642 IPython.CodeCell = CodeCell;
642
643
643 return {'CodeCell': CodeCell};
644 return {'CodeCell': CodeCell};
644 });
645 });
@@ -1,988 +1,986 b''
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 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jqueryui',
6 'jqueryui',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/security',
8 'base/js/security',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'notebook/js/mathjaxutils',
10 'notebook/js/mathjaxutils',
11 'components/marked/lib/marked',
11 'components/marked/lib/marked',
12 ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
12 ], function(IPython, $, utils, security, keyboard, mathjaxutils, marked) {
13 "use strict";
13 "use strict";
14
14
15 /**
15 /**
16 * @class OutputArea
16 * @class OutputArea
17 *
17 *
18 * @constructor
18 * @constructor
19 */
19 */
20
20
21 var OutputArea = function (options) {
21 var OutputArea = function (options) {
22 this.selector = options.selector;
22 this.selector = options.selector;
23 this.events = options.events;
23 this.events = options.events;
24 this.keyboard_manager = options.keyboard_manager;
24 this.keyboard_manager = options.keyboard_manager;
25 this.wrapper = $(options.selector);
25 this.wrapper = $(options.selector);
26 this.outputs = [];
26 this.outputs = [];
27 this.collapsed = false;
27 this.collapsed = false;
28 this.scrolled = false;
28 this.scrolled = false;
29 this.trusted = true;
29 this.trusted = true;
30 this.clear_queued = null;
30 this.clear_queued = null;
31 if (options.prompt_area === undefined) {
31 if (options.prompt_area === undefined) {
32 this.prompt_area = true;
32 this.prompt_area = true;
33 } else {
33 } else {
34 this.prompt_area = options.prompt_area;
34 this.prompt_area = options.prompt_area;
35 }
35 }
36 this.create_elements();
36 this.create_elements();
37 this.style();
37 this.style();
38 this.bind_events();
38 this.bind_events();
39 };
39 };
40
40
41
41
42 /**
42 /**
43 * Class prototypes
43 * Class prototypes
44 **/
44 **/
45
45
46 OutputArea.prototype.create_elements = function () {
46 OutputArea.prototype.create_elements = function () {
47 this.element = $("<div/>");
47 this.element = $("<div/>");
48 this.collapse_button = $("<div/>");
48 this.collapse_button = $("<div/>");
49 this.prompt_overlay = $("<div/>");
49 this.prompt_overlay = $("<div/>");
50 this.wrapper.append(this.prompt_overlay);
50 this.wrapper.append(this.prompt_overlay);
51 this.wrapper.append(this.element);
51 this.wrapper.append(this.element);
52 this.wrapper.append(this.collapse_button);
52 this.wrapper.append(this.collapse_button);
53 };
53 };
54
54
55
55
56 OutputArea.prototype.style = function () {
56 OutputArea.prototype.style = function () {
57 this.collapse_button.hide();
57 this.collapse_button.hide();
58 this.prompt_overlay.hide();
58 this.prompt_overlay.hide();
59
59
60 this.wrapper.addClass('output_wrapper');
60 this.wrapper.addClass('output_wrapper');
61 this.element.addClass('output');
61 this.element.addClass('output');
62
62
63 this.collapse_button.addClass("btn btn-default output_collapsed");
63 this.collapse_button.addClass("btn btn-default output_collapsed");
64 this.collapse_button.attr('title', 'click to expand output');
64 this.collapse_button.attr('title', 'click to expand output');
65 this.collapse_button.text('. . .');
65 this.collapse_button.text('. . .');
66
66
67 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 this.prompt_overlay.addClass('out_prompt_overlay prompt');
68 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
69
69
70 this.collapse();
70 this.collapse();
71 };
71 };
72
72
73 /**
73 /**
74 * Should the OutputArea scroll?
74 * Should the OutputArea scroll?
75 * Returns whether the height (in lines) exceeds a threshold.
75 * Returns whether the height (in lines) exceeds a threshold.
76 *
76 *
77 * @private
77 * @private
78 * @method _should_scroll
78 * @method _should_scroll
79 * @param [lines=100]{Integer}
79 * @param [lines=100]{Integer}
80 * @return {Bool}
80 * @return {Bool}
81 *
81 *
82 */
82 */
83 OutputArea.prototype._should_scroll = function (lines) {
83 OutputArea.prototype._should_scroll = function (lines) {
84 if (lines <=0 ){ return; }
84 if (lines <=0 ){ return; }
85 if (!lines) {
85 if (!lines) {
86 lines = 100;
86 lines = 100;
87 }
87 }
88 // line-height from http://stackoverflow.com/questions/1185151
88 // line-height from http://stackoverflow.com/questions/1185151
89 var fontSize = this.element.css('font-size');
89 var fontSize = this.element.css('font-size');
90 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
91
91
92 return (this.element.height() > lines * lineHeight);
92 return (this.element.height() > lines * lineHeight);
93 };
93 };
94
94
95
95
96 OutputArea.prototype.bind_events = function () {
96 OutputArea.prototype.bind_events = function () {
97 var that = this;
97 var that = this;
98 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
99 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99 this.prompt_overlay.click(function () { that.toggle_scroll(); });
100
100
101 this.element.resize(function () {
101 this.element.resize(function () {
102 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
103 if ( utils.browser[0] === "Firefox" ) {
103 if ( utils.browser[0] === "Firefox" ) {
104 return;
104 return;
105 }
105 }
106 // maybe scroll output,
106 // maybe scroll output,
107 // if it's grown large enough and hasn't already been scrolled.
107 // if it's grown large enough and hasn't already been scrolled.
108 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
109 that.scroll_area();
109 that.scroll_area();
110 }
110 }
111 });
111 });
112 this.collapse_button.click(function () {
112 this.collapse_button.click(function () {
113 that.expand();
113 that.expand();
114 });
114 });
115 };
115 };
116
116
117
117
118 OutputArea.prototype.collapse = function () {
118 OutputArea.prototype.collapse = function () {
119 if (!this.collapsed) {
119 if (!this.collapsed) {
120 this.element.hide();
120 this.element.hide();
121 this.prompt_overlay.hide();
121 this.prompt_overlay.hide();
122 if (this.element.html()){
122 if (this.element.html()){
123 this.collapse_button.show();
123 this.collapse_button.show();
124 }
124 }
125 this.collapsed = true;
125 this.collapsed = true;
126 }
126 }
127 };
127 };
128
128
129
129
130 OutputArea.prototype.expand = function () {
130 OutputArea.prototype.expand = function () {
131 if (this.collapsed) {
131 if (this.collapsed) {
132 this.collapse_button.hide();
132 this.collapse_button.hide();
133 this.element.show();
133 this.element.show();
134 this.prompt_overlay.show();
134 this.prompt_overlay.show();
135 this.collapsed = false;
135 this.collapsed = false;
136 }
136 }
137 };
137 };
138
138
139
139
140 OutputArea.prototype.toggle_output = function () {
140 OutputArea.prototype.toggle_output = function () {
141 if (this.collapsed) {
141 if (this.collapsed) {
142 this.expand();
142 this.expand();
143 } else {
143 } else {
144 this.collapse();
144 this.collapse();
145 }
145 }
146 };
146 };
147
147
148
148
149 OutputArea.prototype.scroll_area = function () {
149 OutputArea.prototype.scroll_area = function () {
150 this.element.addClass('output_scroll');
150 this.element.addClass('output_scroll');
151 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
152 this.scrolled = true;
152 this.scrolled = true;
153 };
153 };
154
154
155
155
156 OutputArea.prototype.unscroll_area = function () {
156 OutputArea.prototype.unscroll_area = function () {
157 this.element.removeClass('output_scroll');
157 this.element.removeClass('output_scroll');
158 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
159 this.scrolled = false;
159 this.scrolled = false;
160 };
160 };
161
161
162 /**
162 /**
163 *
163 *
164 * Scroll OutputArea if height supperior than a threshold (in lines).
164 * Scroll OutputArea if height supperior than a threshold (in lines).
165 *
165 *
166 * Threshold is a maximum number of lines. If unspecified, defaults to
166 * Threshold is a maximum number of lines. If unspecified, defaults to
167 * OutputArea.minimum_scroll_threshold.
167 * OutputArea.minimum_scroll_threshold.
168 *
168 *
169 * Negative threshold will prevent the OutputArea from ever scrolling.
169 * Negative threshold will prevent the OutputArea from ever scrolling.
170 *
170 *
171 * @method scroll_if_long
171 * @method scroll_if_long
172 *
172 *
173 * @param [lines=20]{Number} Default to 20 if not set,
173 * @param [lines=20]{Number} Default to 20 if not set,
174 * behavior undefined for value of `0`.
174 * behavior undefined for value of `0`.
175 *
175 *
176 **/
176 **/
177 OutputArea.prototype.scroll_if_long = function (lines) {
177 OutputArea.prototype.scroll_if_long = function (lines) {
178 var n = lines || OutputArea.minimum_scroll_threshold;
178 var n = lines || OutputArea.minimum_scroll_threshold;
179 if(n <= 0){
179 if(n <= 0){
180 return;
180 return;
181 }
181 }
182
182
183 if (this._should_scroll(n)) {
183 if (this._should_scroll(n)) {
184 // only allow scrolling long-enough output
184 // only allow scrolling long-enough output
185 this.scroll_area();
185 this.scroll_area();
186 }
186 }
187 };
187 };
188
188
189
189
190 OutputArea.prototype.toggle_scroll = function () {
190 OutputArea.prototype.toggle_scroll = function () {
191 if (this.scrolled) {
191 if (this.scrolled) {
192 this.unscroll_area();
192 this.unscroll_area();
193 } else {
193 } else {
194 // only allow scrolling long-enough output
194 // only allow scrolling long-enough output
195 this.scroll_if_long();
195 this.scroll_if_long();
196 }
196 }
197 };
197 };
198
198
199
199
200 // typeset with MathJax if MathJax is available
200 // typeset with MathJax if MathJax is available
201 OutputArea.prototype.typeset = function () {
201 OutputArea.prototype.typeset = function () {
202 utils.typeset(this.element);
202 utils.typeset(this.element);
203 };
203 };
204
204
205
205
206 OutputArea.prototype.handle_output = function (msg) {
206 OutputArea.prototype.handle_output = function (msg) {
207 var json = {};
207 var json = {};
208 var msg_type = json.output_type = msg.header.msg_type;
208 var msg_type = json.output_type = msg.header.msg_type;
209 var content = msg.content;
209 var content = msg.content;
210 if (msg_type === "stream") {
210 if (msg_type === "stream") {
211 json.text = content.text;
211 json.text = content.text;
212 json.name = content.name;
212 json.name = content.name;
213 } else if (msg_type === "display_data") {
213 } else if (msg_type === "display_data") {
214 json.data = content.data;
214 json.data = content.data;
215 json.output_type = msg_type;
216 json.metadata = content.metadata;
215 json.metadata = content.metadata;
217 } else if (msg_type === "execute_result") {
216 } else if (msg_type === "execute_result") {
218 json.data = content.data;
217 json.data = content.data;
219 json.output_type = msg_type;
220 json.metadata = content.metadata;
218 json.metadata = content.metadata;
221 json.execution_count = content.execution_count;
219 json.execution_count = content.execution_count;
222 } else if (msg_type === "error") {
220 } else if (msg_type === "error") {
223 json.ename = content.ename;
221 json.ename = content.ename;
224 json.evalue = content.evalue;
222 json.evalue = content.evalue;
225 json.traceback = content.traceback;
223 json.traceback = content.traceback;
226 } else {
224 } else {
227 console.log("unhandled output message", msg);
225 console.log("unhandled output message", msg);
228 return;
226 return;
229 }
227 }
230 this.append_output(json);
228 this.append_output(json);
231 };
229 };
232
230
233
231
234 OutputArea.output_types = [
232 OutputArea.output_types = [
235 'application/javascript',
233 'application/javascript',
236 'text/html',
234 'text/html',
237 'text/markdown',
235 'text/markdown',
238 'text/latex',
236 'text/latex',
239 'image/svg+xml',
237 'image/svg+xml',
240 'image/png',
238 'image/png',
241 'image/jpeg',
239 'image/jpeg',
242 'application/pdf',
240 'application/pdf',
243 'text/plain'
241 'text/plain'
244 ];
242 ];
245
243
246 OutputArea.prototype.validate_mimebundle = function (bundle) {
244 OutputArea.prototype.validate_mimebundle = function (bundle) {
247 /** scrub invalid outputs */
245 /** scrub invalid outputs */
248 if (typeof bundle.data !== 'object') {
246 if (typeof bundle.data !== 'object') {
249 console.warn("mimebundle missing data", bundle);
247 console.warn("mimebundle missing data", bundle);
250 bundle.data = {};
248 bundle.data = {};
251 }
249 }
252 if (typeof bundle.metadata !== 'object') {
250 if (typeof bundle.metadata !== 'object') {
253 console.warn("mimebundle missing metadata", bundle);
251 console.warn("mimebundle missing metadata", bundle);
254 bundle.metadata = {};
252 bundle.metadata = {};
255 }
253 }
256 var data = bundle.data;
254 var data = bundle.data;
257 $.map(OutputArea.output_types, function(key){
255 $.map(OutputArea.output_types, function(key){
258 if (key !== 'application/json' &&
256 if (key !== 'application/json' &&
259 data[key] !== undefined &&
257 data[key] !== undefined &&
260 typeof data[key] !== 'string'
258 typeof data[key] !== 'string'
261 ) {
259 ) {
262 console.log("Invalid type for " + key, data[key]);
260 console.log("Invalid type for " + key, data[key]);
263 delete data[key];
261 delete data[key];
264 }
262 }
265 });
263 });
266 return bundle;
264 return bundle;
267 };
265 };
268
266
269 OutputArea.prototype.append_output = function (json) {
267 OutputArea.prototype.append_output = function (json) {
270 this.expand();
268 this.expand();
271
269
272 // Clear the output if clear is queued.
270 // Clear the output if clear is queued.
273 var needs_height_reset = false;
271 var needs_height_reset = false;
274 if (this.clear_queued) {
272 if (this.clear_queued) {
275 this.clear_output(false);
273 this.clear_output(false);
276 needs_height_reset = true;
274 needs_height_reset = true;
277 }
275 }
278
276
279 var record_output = true;
277 var record_output = true;
280 switch(json.output_type) {
278 switch(json.output_type) {
281 case 'execute_result':
279 case 'execute_result':
282 json = this.validate_mimebundle(json);
280 json = this.validate_mimebundle(json);
283 this.append_execute_result(json);
281 this.append_execute_result(json);
284 break;
282 break;
285 case 'stream':
283 case 'stream':
286 // append_stream might have merged the output with earlier stream output
284 // append_stream might have merged the output with earlier stream output
287 record_output = this.append_stream(json);
285 record_output = this.append_stream(json);
288 break;
286 break;
289 case 'error':
287 case 'error':
290 this.append_error(json);
288 this.append_error(json);
291 break;
289 break;
292 case 'display_data':
290 case 'display_data':
293 // append handled below
291 // append handled below
294 json = this.validate_mimebundle(json);
292 json = this.validate_mimebundle(json);
295 break;
293 break;
296 default:
294 default:
297 console.log("unrecognized output type: " + json.output_type);
295 console.log("unrecognized output type: " + json.output_type);
298 this.append_unrecognized(json);
296 this.append_unrecognized(json);
299 }
297 }
300
298
301 // We must release the animation fixed height in a callback since Gecko
299 // We must release the animation fixed height in a callback since Gecko
302 // (FireFox) doesn't render the image immediately as the data is
300 // (FireFox) doesn't render the image immediately as the data is
303 // available.
301 // available.
304 var that = this;
302 var that = this;
305 var handle_appended = function ($el) {
303 var handle_appended = function ($el) {
306 /**
304 /**
307 * Only reset the height to automatic if the height is currently
305 * Only reset the height to automatic if the height is currently
308 * fixed (done by wait=True flag on clear_output).
306 * fixed (done by wait=True flag on clear_output).
309 */
307 */
310 if (needs_height_reset) {
308 if (needs_height_reset) {
311 that.element.height('');
309 that.element.height('');
312 }
310 }
313 that.element.trigger('resize');
311 that.element.trigger('resize');
314 };
312 };
315 if (json.output_type === 'display_data') {
313 if (json.output_type === 'display_data') {
316 this.append_display_data(json, handle_appended);
314 this.append_display_data(json, handle_appended);
317 } else {
315 } else {
318 handle_appended();
316 handle_appended();
319 }
317 }
320
318
321 if (record_output) {
319 if (record_output) {
322 this.outputs.push(json);
320 this.outputs.push(json);
323 }
321 }
324 };
322 };
325
323
326
324
327 OutputArea.prototype.create_output_area = function () {
325 OutputArea.prototype.create_output_area = function () {
328 var oa = $("<div/>").addClass("output_area");
326 var oa = $("<div/>").addClass("output_area");
329 if (this.prompt_area) {
327 if (this.prompt_area) {
330 oa.append($('<div/>').addClass('prompt'));
328 oa.append($('<div/>').addClass('prompt'));
331 }
329 }
332 return oa;
330 return oa;
333 };
331 };
334
332
335
333
336 function _get_metadata_key(metadata, key, mime) {
334 function _get_metadata_key(metadata, key, mime) {
337 var mime_md = metadata[mime];
335 var mime_md = metadata[mime];
338 // mime-specific higher priority
336 // mime-specific higher priority
339 if (mime_md && mime_md[key] !== undefined) {
337 if (mime_md && mime_md[key] !== undefined) {
340 return mime_md[key];
338 return mime_md[key];
341 }
339 }
342 // fallback on global
340 // fallback on global
343 return metadata[key];
341 return metadata[key];
344 }
342 }
345
343
346 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
344 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
347 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
345 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
348 if (_get_metadata_key(md, 'isolated', mime)) {
346 if (_get_metadata_key(md, 'isolated', mime)) {
349 // Create an iframe to isolate the subarea from the rest of the
347 // Create an iframe to isolate the subarea from the rest of the
350 // document
348 // document
351 var iframe = $('<iframe/>').addClass('box-flex1');
349 var iframe = $('<iframe/>').addClass('box-flex1');
352 iframe.css({'height':1, 'width':'100%', 'display':'block'});
350 iframe.css({'height':1, 'width':'100%', 'display':'block'});
353 iframe.attr('frameborder', 0);
351 iframe.attr('frameborder', 0);
354 iframe.attr('scrolling', 'auto');
352 iframe.attr('scrolling', 'auto');
355
353
356 // Once the iframe is loaded, the subarea is dynamically inserted
354 // Once the iframe is loaded, the subarea is dynamically inserted
357 iframe.on('load', function() {
355 iframe.on('load', function() {
358 // Workaround needed by Firefox, to properly render svg inside
356 // Workaround needed by Firefox, to properly render svg inside
359 // iframes, see http://stackoverflow.com/questions/10177190/
357 // iframes, see http://stackoverflow.com/questions/10177190/
360 // svg-dynamically-added-to-iframe-does-not-render-correctly
358 // svg-dynamically-added-to-iframe-does-not-render-correctly
361 this.contentDocument.open();
359 this.contentDocument.open();
362
360
363 // Insert the subarea into the iframe
361 // Insert the subarea into the iframe
364 // We must directly write the html. When using Jquery's append
362 // We must directly write the html. When using Jquery's append
365 // method, javascript is evaluated in the parent document and
363 // method, javascript is evaluated in the parent document and
366 // not in the iframe document. At this point, subarea doesn't
364 // not in the iframe document. At this point, subarea doesn't
367 // contain any user content.
365 // contain any user content.
368 this.contentDocument.write(subarea.html());
366 this.contentDocument.write(subarea.html());
369
367
370 this.contentDocument.close();
368 this.contentDocument.close();
371
369
372 var body = this.contentDocument.body;
370 var body = this.contentDocument.body;
373 // Adjust the iframe height automatically
371 // Adjust the iframe height automatically
374 iframe.height(body.scrollHeight + 'px');
372 iframe.height(body.scrollHeight + 'px');
375 });
373 });
376
374
377 // Elements should be appended to the inner subarea and not to the
375 // Elements should be appended to the inner subarea and not to the
378 // iframe
376 // iframe
379 iframe.append = function(that) {
377 iframe.append = function(that) {
380 subarea.append(that);
378 subarea.append(that);
381 };
379 };
382
380
383 return iframe;
381 return iframe;
384 } else {
382 } else {
385 return subarea;
383 return subarea;
386 }
384 }
387 };
385 };
388
386
389
387
390 OutputArea.prototype._append_javascript_error = function (err, element) {
388 OutputArea.prototype._append_javascript_error = function (err, element) {
391 /**
389 /**
392 * display a message when a javascript error occurs in display output
390 * display a message when a javascript error occurs in display output
393 */
391 */
394 var msg = "Javascript error adding output!";
392 var msg = "Javascript error adding output!";
395 if ( element === undefined ) return;
393 if ( element === undefined ) return;
396 element
394 element
397 .append($('<div/>').text(msg).addClass('js-error'))
395 .append($('<div/>').text(msg).addClass('js-error'))
398 .append($('<div/>').text(err.toString()).addClass('js-error'))
396 .append($('<div/>').text(err.toString()).addClass('js-error'))
399 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
397 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
400 };
398 };
401
399
402 OutputArea.prototype._safe_append = function (toinsert) {
400 OutputArea.prototype._safe_append = function (toinsert) {
403 /**
401 /**
404 * safely append an item to the document
402 * safely append an item to the document
405 * this is an object created by user code,
403 * this is an object created by user code,
406 * and may have errors, which should not be raised
404 * and may have errors, which should not be raised
407 * under any circumstances.
405 * under any circumstances.
408 */
406 */
409 try {
407 try {
410 this.element.append(toinsert);
408 this.element.append(toinsert);
411 } catch(err) {
409 } catch(err) {
412 console.log(err);
410 console.log(err);
413 // Create an actual output_area and output_subarea, which creates
411 // Create an actual output_area and output_subarea, which creates
414 // the prompt area and the proper indentation.
412 // the prompt area and the proper indentation.
415 var toinsert = this.create_output_area();
413 var toinsert = this.create_output_area();
416 var subarea = $('<div/>').addClass('output_subarea');
414 var subarea = $('<div/>').addClass('output_subarea');
417 toinsert.append(subarea);
415 toinsert.append(subarea);
418 this._append_javascript_error(err, subarea);
416 this._append_javascript_error(err, subarea);
419 this.element.append(toinsert);
417 this.element.append(toinsert);
420 }
418 }
421
419
422 // Notify others of changes.
420 // Notify others of changes.
423 this.element.trigger('changed');
421 this.element.trigger('changed');
424 };
422 };
425
423
426
424
427 OutputArea.prototype.append_execute_result = function (json) {
425 OutputArea.prototype.append_execute_result = function (json) {
428 var n = json.execution_count || ' ';
426 var n = json.execution_count || ' ';
429 var toinsert = this.create_output_area();
427 var toinsert = this.create_output_area();
430 if (this.prompt_area) {
428 if (this.prompt_area) {
431 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
429 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
432 }
430 }
433 var inserted = this.append_mime_type(json, toinsert);
431 var inserted = this.append_mime_type(json, toinsert);
434 if (inserted) {
432 if (inserted) {
435 inserted.addClass('output_result');
433 inserted.addClass('output_result');
436 }
434 }
437 this._safe_append(toinsert);
435 this._safe_append(toinsert);
438 // If we just output latex, typeset it.
436 // If we just output latex, typeset it.
439 if ((json.data['text/latex'] !== undefined) ||
437 if ((json.data['text/latex'] !== undefined) ||
440 (json.data['text/html'] !== undefined) ||
438 (json.data['text/html'] !== undefined) ||
441 (json.data['text/markdown'] !== undefined)) {
439 (json.data['text/markdown'] !== undefined)) {
442 this.typeset();
440 this.typeset();
443 }
441 }
444 };
442 };
445
443
446
444
447 OutputArea.prototype.append_error = function (json) {
445 OutputArea.prototype.append_error = function (json) {
448 var tb = json.traceback;
446 var tb = json.traceback;
449 if (tb !== undefined && tb.length > 0) {
447 if (tb !== undefined && tb.length > 0) {
450 var s = '';
448 var s = '';
451 var len = tb.length;
449 var len = tb.length;
452 for (var i=0; i<len; i++) {
450 for (var i=0; i<len; i++) {
453 s = s + tb[i] + '\n';
451 s = s + tb[i] + '\n';
454 }
452 }
455 s = s + '\n';
453 s = s + '\n';
456 var toinsert = this.create_output_area();
454 var toinsert = this.create_output_area();
457 var append_text = OutputArea.append_map['text/plain'];
455 var append_text = OutputArea.append_map['text/plain'];
458 if (append_text) {
456 if (append_text) {
459 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
457 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
460 }
458 }
461 this._safe_append(toinsert);
459 this._safe_append(toinsert);
462 }
460 }
463 };
461 };
464
462
465
463
466 OutputArea.prototype.append_stream = function (json) {
464 OutputArea.prototype.append_stream = function (json) {
467 var text = json.text;
465 var text = json.text;
468 if (typeof text !== 'string') {
466 if (typeof text !== 'string') {
469 console.error("Stream output is invalid (missing text)", json);
467 console.error("Stream output is invalid (missing text)", json);
470 return false;
468 return false;
471 }
469 }
472 var subclass = "output_"+json.name;
470 var subclass = "output_"+json.name;
473 if (this.outputs.length > 0){
471 if (this.outputs.length > 0){
474 // have at least one output to consider
472 // have at least one output to consider
475 var last = this.outputs[this.outputs.length-1];
473 var last = this.outputs[this.outputs.length-1];
476 if (last.output_type == 'stream' && json.name == last.name){
474 if (last.output_type == 'stream' && json.name == last.name){
477 // latest output was in the same stream,
475 // latest output was in the same stream,
478 // so append directly into its pre tag
476 // so append directly into its pre tag
479 // escape ANSI & HTML specials:
477 // escape ANSI & HTML specials:
480 last.text = utils.fixCarriageReturn(last.text + json.text);
478 last.text = utils.fixCarriageReturn(last.text + json.text);
481 var pre = this.element.find('div.'+subclass).last().find('pre');
479 var pre = this.element.find('div.'+subclass).last().find('pre');
482 var html = utils.fixConsole(last.text);
480 var html = utils.fixConsole(last.text);
483 // The only user content injected with this HTML call is
481 // The only user content injected with this HTML call is
484 // escaped by the fixConsole() method.
482 // escaped by the fixConsole() method.
485 pre.html(html);
483 pre.html(html);
486 // return false signals that we merged this output with the previous one,
484 // return false signals that we merged this output with the previous one,
487 // and the new output shouldn't be recorded.
485 // and the new output shouldn't be recorded.
488 return false;
486 return false;
489 }
487 }
490 }
488 }
491
489
492 if (!text.replace("\r", "")) {
490 if (!text.replace("\r", "")) {
493 // text is nothing (empty string, \r, etc.)
491 // text is nothing (empty string, \r, etc.)
494 // so don't append any elements, which might add undesirable space
492 // so don't append any elements, which might add undesirable space
495 // return true to indicate the output should be recorded.
493 // return true to indicate the output should be recorded.
496 return true;
494 return true;
497 }
495 }
498
496
499 // If we got here, attach a new div
497 // If we got here, attach a new div
500 var toinsert = this.create_output_area();
498 var toinsert = this.create_output_area();
501 var append_text = OutputArea.append_map['text/plain'];
499 var append_text = OutputArea.append_map['text/plain'];
502 if (append_text) {
500 if (append_text) {
503 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
501 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
504 }
502 }
505 this._safe_append(toinsert);
503 this._safe_append(toinsert);
506 return true;
504 return true;
507 };
505 };
508
506
509
507
510 OutputArea.prototype.append_unrecognized = function (json) {
508 OutputArea.prototype.append_unrecognized = function (json) {
511 var that = this;
509 var that = this;
512 var toinsert = this.create_output_area();
510 var toinsert = this.create_output_area();
513 var subarea = $('<div/>').addClass('output_subarea output_unrecognized');
511 var subarea = $('<div/>').addClass('output_subarea output_unrecognized');
514 toinsert.append(subarea);
512 toinsert.append(subarea);
515 subarea.append(
513 subarea.append(
516 $("<a>")
514 $("<a>")
517 .attr("href", "#")
515 .attr("href", "#")
518 .text("Unrecognized output: " + json.output_type)
516 .text("Unrecognized output: " + json.output_type)
519 .click(function () {
517 .click(function () {
520 that.events.trigger('unrecognized_output.OutputArea', {output: json})
518 that.events.trigger('unrecognized_output.OutputArea', {output: json})
521 })
519 })
522 );
520 );
523 this._safe_append(toinsert);
521 this._safe_append(toinsert);
524 };
522 };
525
523
526
524
527 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
525 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
528 var toinsert = this.create_output_area();
526 var toinsert = this.create_output_area();
529 if (this.append_mime_type(json, toinsert, handle_inserted)) {
527 if (this.append_mime_type(json, toinsert, handle_inserted)) {
530 this._safe_append(toinsert);
528 this._safe_append(toinsert);
531 // If we just output latex, typeset it.
529 // If we just output latex, typeset it.
532 if ((json.data['text/latex'] !== undefined) ||
530 if ((json.data['text/latex'] !== undefined) ||
533 (json.data['text/html'] !== undefined) ||
531 (json.data['text/html'] !== undefined) ||
534 (json.data['text/markdown'] !== undefined)) {
532 (json.data['text/markdown'] !== undefined)) {
535 this.typeset();
533 this.typeset();
536 }
534 }
537 }
535 }
538 };
536 };
539
537
540
538
541 OutputArea.safe_outputs = {
539 OutputArea.safe_outputs = {
542 'text/plain' : true,
540 'text/plain' : true,
543 'text/latex' : true,
541 'text/latex' : true,
544 'image/png' : true,
542 'image/png' : true,
545 'image/jpeg' : true
543 'image/jpeg' : true
546 };
544 };
547
545
548 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
546 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
549 for (var i=0; i < OutputArea.display_order.length; i++) {
547 for (var i=0; i < OutputArea.display_order.length; i++) {
550 var type = OutputArea.display_order[i];
548 var type = OutputArea.display_order[i];
551 var append = OutputArea.append_map[type];
549 var append = OutputArea.append_map[type];
552 if ((json.data[type] !== undefined) && append) {
550 if ((json.data[type] !== undefined) && append) {
553 var value = json.data[type];
551 var value = json.data[type];
554 if (!this.trusted && !OutputArea.safe_outputs[type]) {
552 if (!this.trusted && !OutputArea.safe_outputs[type]) {
555 // not trusted, sanitize HTML
553 // not trusted, sanitize HTML
556 if (type==='text/html' || type==='text/svg') {
554 if (type==='text/html' || type==='text/svg') {
557 value = security.sanitize_html(value);
555 value = security.sanitize_html(value);
558 } else {
556 } else {
559 // don't display if we don't know how to sanitize it
557 // don't display if we don't know how to sanitize it
560 console.log("Ignoring untrusted " + type + " output.");
558 console.log("Ignoring untrusted " + type + " output.");
561 continue;
559 continue;
562 }
560 }
563 }
561 }
564 var md = json.metadata || {};
562 var md = json.metadata || {};
565 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
563 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
566 // Since only the png and jpeg mime types call the inserted
564 // Since only the png and jpeg mime types call the inserted
567 // callback, if the mime type is something other we must call the
565 // callback, if the mime type is something other we must call the
568 // inserted callback only when the element is actually inserted
566 // inserted callback only when the element is actually inserted
569 // into the DOM. Use a timeout of 0 to do this.
567 // into the DOM. Use a timeout of 0 to do this.
570 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
568 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
571 setTimeout(handle_inserted, 0);
569 setTimeout(handle_inserted, 0);
572 }
570 }
573 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
571 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
574 return toinsert;
572 return toinsert;
575 }
573 }
576 }
574 }
577 return null;
575 return null;
578 };
576 };
579
577
580
578
581 var append_html = function (html, md, element) {
579 var append_html = function (html, md, element) {
582 var type = 'text/html';
580 var type = 'text/html';
583 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
581 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
584 this.keyboard_manager.register_events(toinsert);
582 this.keyboard_manager.register_events(toinsert);
585 toinsert.append(html);
583 toinsert.append(html);
586 element.append(toinsert);
584 element.append(toinsert);
587 return toinsert;
585 return toinsert;
588 };
586 };
589
587
590
588
591 var append_markdown = function(markdown, md, element) {
589 var append_markdown = function(markdown, md, element) {
592 var type = 'text/markdown';
590 var type = 'text/markdown';
593 var toinsert = this.create_output_subarea(md, "output_markdown", type);
591 var toinsert = this.create_output_subarea(md, "output_markdown", type);
594 var text_and_math = mathjaxutils.remove_math(markdown);
592 var text_and_math = mathjaxutils.remove_math(markdown);
595 var text = text_and_math[0];
593 var text = text_and_math[0];
596 var math = text_and_math[1];
594 var math = text_and_math[1];
597 marked(text, function (err, html) {
595 marked(text, function (err, html) {
598 html = mathjaxutils.replace_math(html, math);
596 html = mathjaxutils.replace_math(html, math);
599 toinsert.append(html);
597 toinsert.append(html);
600 });
598 });
601 element.append(toinsert);
599 element.append(toinsert);
602 return toinsert;
600 return toinsert;
603 };
601 };
604
602
605
603
606 var append_javascript = function (js, md, element) {
604 var append_javascript = function (js, md, element) {
607 /**
605 /**
608 * We just eval the JS code, element appears in the local scope.
606 * We just eval the JS code, element appears in the local scope.
609 */
607 */
610 var type = 'application/javascript';
608 var type = 'application/javascript';
611 var toinsert = this.create_output_subarea(md, "output_javascript", type);
609 var toinsert = this.create_output_subarea(md, "output_javascript", type);
612 this.keyboard_manager.register_events(toinsert);
610 this.keyboard_manager.register_events(toinsert);
613 element.append(toinsert);
611 element.append(toinsert);
614
612
615 // Fix for ipython/issues/5293, make sure `element` is the area which
613 // Fix for ipython/issues/5293, make sure `element` is the area which
616 // output can be inserted into at the time of JS execution.
614 // output can be inserted into at the time of JS execution.
617 element = toinsert;
615 element = toinsert;
618 try {
616 try {
619 eval(js);
617 eval(js);
620 } catch(err) {
618 } catch(err) {
621 console.log(err);
619 console.log(err);
622 this._append_javascript_error(err, toinsert);
620 this._append_javascript_error(err, toinsert);
623 }
621 }
624 return toinsert;
622 return toinsert;
625 };
623 };
626
624
627
625
628 var append_text = function (data, md, element) {
626 var append_text = function (data, md, element) {
629 var type = 'text/plain';
627 var type = 'text/plain';
630 var toinsert = this.create_output_subarea(md, "output_text", type);
628 var toinsert = this.create_output_subarea(md, "output_text", type);
631 // escape ANSI & HTML specials in plaintext:
629 // escape ANSI & HTML specials in plaintext:
632 data = utils.fixConsole(data);
630 data = utils.fixConsole(data);
633 data = utils.fixCarriageReturn(data);
631 data = utils.fixCarriageReturn(data);
634 data = utils.autoLinkUrls(data);
632 data = utils.autoLinkUrls(data);
635 // The only user content injected with this HTML call is
633 // The only user content injected with this HTML call is
636 // escaped by the fixConsole() method.
634 // escaped by the fixConsole() method.
637 toinsert.append($("<pre/>").html(data));
635 toinsert.append($("<pre/>").html(data));
638 element.append(toinsert);
636 element.append(toinsert);
639 return toinsert;
637 return toinsert;
640 };
638 };
641
639
642
640
643 var append_svg = function (svg_html, md, element) {
641 var append_svg = function (svg_html, md, element) {
644 var type = 'image/svg+xml';
642 var type = 'image/svg+xml';
645 var toinsert = this.create_output_subarea(md, "output_svg", type);
643 var toinsert = this.create_output_subarea(md, "output_svg", type);
646
644
647 // Get the svg element from within the HTML.
645 // Get the svg element from within the HTML.
648 var svg = $('<div />').html(svg_html).find('svg');
646 var svg = $('<div />').html(svg_html).find('svg');
649 var svg_area = $('<div />');
647 var svg_area = $('<div />');
650 var width = svg.attr('width');
648 var width = svg.attr('width');
651 var height = svg.attr('height');
649 var height = svg.attr('height');
652 svg
650 svg
653 .width('100%')
651 .width('100%')
654 .height('100%');
652 .height('100%');
655 svg_area
653 svg_area
656 .width(width)
654 .width(width)
657 .height(height);
655 .height(height);
658
656
659 // The jQuery resize handlers don't seem to work on the svg element.
657 // The jQuery resize handlers don't seem to work on the svg element.
660 // When the svg renders completely, measure it's size and set the parent
658 // When the svg renders completely, measure it's size and set the parent
661 // div to that size. Then set the svg to 100% the size of the parent
659 // div to that size. Then set the svg to 100% the size of the parent
662 // div and make the parent div resizable.
660 // div and make the parent div resizable.
663 this._dblclick_to_reset_size(svg_area, true, false);
661 this._dblclick_to_reset_size(svg_area, true, false);
664
662
665 svg_area.append(svg);
663 svg_area.append(svg);
666 toinsert.append(svg_area);
664 toinsert.append(svg_area);
667 element.append(toinsert);
665 element.append(toinsert);
668
666
669 return toinsert;
667 return toinsert;
670 };
668 };
671
669
672 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
670 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
673 /**
671 /**
674 * Add a resize handler to an element
672 * Add a resize handler to an element
675 *
673 *
676 * img: jQuery element
674 * img: jQuery element
677 * immediately: bool=False
675 * immediately: bool=False
678 * Wait for the element to load before creating the handle.
676 * Wait for the element to load before creating the handle.
679 * resize_parent: bool=True
677 * resize_parent: bool=True
680 * Should the parent of the element be resized when the element is
678 * Should the parent of the element be resized when the element is
681 * reset (by double click).
679 * reset (by double click).
682 */
680 */
683 var callback = function (){
681 var callback = function (){
684 var h0 = img.height();
682 var h0 = img.height();
685 var w0 = img.width();
683 var w0 = img.width();
686 if (!(h0 && w0)) {
684 if (!(h0 && w0)) {
687 // zero size, don't make it resizable
685 // zero size, don't make it resizable
688 return;
686 return;
689 }
687 }
690 img.resizable({
688 img.resizable({
691 aspectRatio: true,
689 aspectRatio: true,
692 autoHide: true
690 autoHide: true
693 });
691 });
694 img.dblclick(function () {
692 img.dblclick(function () {
695 // resize wrapper & image together for some reason:
693 // resize wrapper & image together for some reason:
696 img.height(h0);
694 img.height(h0);
697 img.width(w0);
695 img.width(w0);
698 if (resize_parent === undefined || resize_parent) {
696 if (resize_parent === undefined || resize_parent) {
699 img.parent().height(h0);
697 img.parent().height(h0);
700 img.parent().width(w0);
698 img.parent().width(w0);
701 }
699 }
702 });
700 });
703 };
701 };
704
702
705 if (immediately) {
703 if (immediately) {
706 callback();
704 callback();
707 } else {
705 } else {
708 img.on("load", callback);
706 img.on("load", callback);
709 }
707 }
710 };
708 };
711
709
712 var set_width_height = function (img, md, mime) {
710 var set_width_height = function (img, md, mime) {
713 /**
711 /**
714 * set width and height of an img element from metadata
712 * set width and height of an img element from metadata
715 */
713 */
716 var height = _get_metadata_key(md, 'height', mime);
714 var height = _get_metadata_key(md, 'height', mime);
717 if (height !== undefined) img.attr('height', height);
715 if (height !== undefined) img.attr('height', height);
718 var width = _get_metadata_key(md, 'width', mime);
716 var width = _get_metadata_key(md, 'width', mime);
719 if (width !== undefined) img.attr('width', width);
717 if (width !== undefined) img.attr('width', width);
720 };
718 };
721
719
722 var append_png = function (png, md, element, handle_inserted) {
720 var append_png = function (png, md, element, handle_inserted) {
723 var type = 'image/png';
721 var type = 'image/png';
724 var toinsert = this.create_output_subarea(md, "output_png", type);
722 var toinsert = this.create_output_subarea(md, "output_png", type);
725 var img = $("<img/>");
723 var img = $("<img/>");
726 if (handle_inserted !== undefined) {
724 if (handle_inserted !== undefined) {
727 img.on('load', function(){
725 img.on('load', function(){
728 handle_inserted(img);
726 handle_inserted(img);
729 });
727 });
730 }
728 }
731 img[0].src = 'data:image/png;base64,'+ png;
729 img[0].src = 'data:image/png;base64,'+ png;
732 set_width_height(img, md, 'image/png');
730 set_width_height(img, md, 'image/png');
733 this._dblclick_to_reset_size(img);
731 this._dblclick_to_reset_size(img);
734 toinsert.append(img);
732 toinsert.append(img);
735 element.append(toinsert);
733 element.append(toinsert);
736 return toinsert;
734 return toinsert;
737 };
735 };
738
736
739
737
740 var append_jpeg = function (jpeg, md, element, handle_inserted) {
738 var append_jpeg = function (jpeg, md, element, handle_inserted) {
741 var type = 'image/jpeg';
739 var type = 'image/jpeg';
742 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
740 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
743 var img = $("<img/>");
741 var img = $("<img/>");
744 if (handle_inserted !== undefined) {
742 if (handle_inserted !== undefined) {
745 img.on('load', function(){
743 img.on('load', function(){
746 handle_inserted(img);
744 handle_inserted(img);
747 });
745 });
748 }
746 }
749 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
747 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
750 set_width_height(img, md, 'image/jpeg');
748 set_width_height(img, md, 'image/jpeg');
751 this._dblclick_to_reset_size(img);
749 this._dblclick_to_reset_size(img);
752 toinsert.append(img);
750 toinsert.append(img);
753 element.append(toinsert);
751 element.append(toinsert);
754 return toinsert;
752 return toinsert;
755 };
753 };
756
754
757
755
758 var append_pdf = function (pdf, md, element) {
756 var append_pdf = function (pdf, md, element) {
759 var type = 'application/pdf';
757 var type = 'application/pdf';
760 var toinsert = this.create_output_subarea(md, "output_pdf", type);
758 var toinsert = this.create_output_subarea(md, "output_pdf", type);
761 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
759 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
762 a.attr('target', '_blank');
760 a.attr('target', '_blank');
763 a.text('View PDF');
761 a.text('View PDF');
764 toinsert.append(a);
762 toinsert.append(a);
765 element.append(toinsert);
763 element.append(toinsert);
766 return toinsert;
764 return toinsert;
767 };
765 };
768
766
769 var append_latex = function (latex, md, element) {
767 var append_latex = function (latex, md, element) {
770 /**
768 /**
771 * This method cannot do the typesetting because the latex first has to
769 * This method cannot do the typesetting because the latex first has to
772 * be on the page.
770 * be on the page.
773 */
771 */
774 var type = 'text/latex';
772 var type = 'text/latex';
775 var toinsert = this.create_output_subarea(md, "output_latex", type);
773 var toinsert = this.create_output_subarea(md, "output_latex", type);
776 toinsert.append(latex);
774 toinsert.append(latex);
777 element.append(toinsert);
775 element.append(toinsert);
778 return toinsert;
776 return toinsert;
779 };
777 };
780
778
781
779
782 OutputArea.prototype.append_raw_input = function (msg) {
780 OutputArea.prototype.append_raw_input = function (msg) {
783 var that = this;
781 var that = this;
784 this.expand();
782 this.expand();
785 var content = msg.content;
783 var content = msg.content;
786 var area = this.create_output_area();
784 var area = this.create_output_area();
787
785
788 // disable any other raw_inputs, if they are left around
786 // disable any other raw_inputs, if they are left around
789 $("div.output_subarea.raw_input_container").remove();
787 $("div.output_subarea.raw_input_container").remove();
790
788
791 var input_type = content.password ? 'password' : 'text';
789 var input_type = content.password ? 'password' : 'text';
792
790
793 area.append(
791 area.append(
794 $("<div/>")
792 $("<div/>")
795 .addClass("box-flex1 output_subarea raw_input_container")
793 .addClass("box-flex1 output_subarea raw_input_container")
796 .append(
794 .append(
797 $("<span/>")
795 $("<span/>")
798 .addClass("raw_input_prompt")
796 .addClass("raw_input_prompt")
799 .text(content.prompt)
797 .text(content.prompt)
800 )
798 )
801 .append(
799 .append(
802 $("<input/>")
800 $("<input/>")
803 .addClass("raw_input")
801 .addClass("raw_input")
804 .attr('type', input_type)
802 .attr('type', input_type)
805 .attr("size", 47)
803 .attr("size", 47)
806 .keydown(function (event, ui) {
804 .keydown(function (event, ui) {
807 // make sure we submit on enter,
805 // make sure we submit on enter,
808 // and don't re-execute the *cell* on shift-enter
806 // and don't re-execute the *cell* on shift-enter
809 if (event.which === keyboard.keycodes.enter) {
807 if (event.which === keyboard.keycodes.enter) {
810 that._submit_raw_input();
808 that._submit_raw_input();
811 return false;
809 return false;
812 }
810 }
813 })
811 })
814 )
812 )
815 );
813 );
816
814
817 this.element.append(area);
815 this.element.append(area);
818 var raw_input = area.find('input.raw_input');
816 var raw_input = area.find('input.raw_input');
819 // Register events that enable/disable the keyboard manager while raw
817 // Register events that enable/disable the keyboard manager while raw
820 // input is focused.
818 // input is focused.
821 this.keyboard_manager.register_events(raw_input);
819 this.keyboard_manager.register_events(raw_input);
822 // Note, the following line used to read raw_input.focus().focus().
820 // Note, the following line used to read raw_input.focus().focus().
823 // This seemed to be needed otherwise only the cell would be focused.
821 // This seemed to be needed otherwise only the cell would be focused.
824 // But with the modal UI, this seems to work fine with one call to focus().
822 // But with the modal UI, this seems to work fine with one call to focus().
825 raw_input.focus();
823 raw_input.focus();
826 };
824 };
827
825
828 OutputArea.prototype._submit_raw_input = function (evt) {
826 OutputArea.prototype._submit_raw_input = function (evt) {
829 var container = this.element.find("div.raw_input_container");
827 var container = this.element.find("div.raw_input_container");
830 var theprompt = container.find("span.raw_input_prompt");
828 var theprompt = container.find("span.raw_input_prompt");
831 var theinput = container.find("input.raw_input");
829 var theinput = container.find("input.raw_input");
832 var value = theinput.val();
830 var value = theinput.val();
833 var echo = value;
831 var echo = value;
834 // don't echo if it's a password
832 // don't echo if it's a password
835 if (theinput.attr('type') == 'password') {
833 if (theinput.attr('type') == 'password') {
836 echo = 'Β·Β·Β·Β·Β·Β·Β·Β·';
834 echo = 'Β·Β·Β·Β·Β·Β·Β·Β·';
837 }
835 }
838 var content = {
836 var content = {
839 output_type : 'stream',
837 output_type : 'stream',
840 name : 'stdout',
838 name : 'stdout',
841 text : theprompt.text() + echo + '\n'
839 text : theprompt.text() + echo + '\n'
842 };
840 };
843 // remove form container
841 // remove form container
844 container.parent().remove();
842 container.parent().remove();
845 // replace with plaintext version in stdout
843 // replace with plaintext version in stdout
846 this.append_output(content, false);
844 this.append_output(content, false);
847 this.events.trigger('send_input_reply.Kernel', value);
845 this.events.trigger('send_input_reply.Kernel', value);
848 };
846 };
849
847
850
848
851 OutputArea.prototype.handle_clear_output = function (msg) {
849 OutputArea.prototype.handle_clear_output = function (msg) {
852 /**
850 /**
853 * msg spec v4 had stdout, stderr, display keys
851 * msg spec v4 had stdout, stderr, display keys
854 * v4.1 replaced these with just wait
852 * v4.1 replaced these with just wait
855 * The default behavior is the same (stdout=stderr=display=True, wait=False),
853 * The default behavior is the same (stdout=stderr=display=True, wait=False),
856 * so v4 messages will still be properly handled,
854 * so v4 messages will still be properly handled,
857 * except for the rarely used clearing less than all output.
855 * except for the rarely used clearing less than all output.
858 */
856 */
859 this.clear_output(msg.content.wait || false);
857 this.clear_output(msg.content.wait || false);
860 };
858 };
861
859
862
860
863 OutputArea.prototype.clear_output = function(wait, ignore_que) {
861 OutputArea.prototype.clear_output = function(wait, ignore_que) {
864 if (wait) {
862 if (wait) {
865
863
866 // If a clear is queued, clear before adding another to the queue.
864 // If a clear is queued, clear before adding another to the queue.
867 if (this.clear_queued) {
865 if (this.clear_queued) {
868 this.clear_output(false);
866 this.clear_output(false);
869 }
867 }
870
868
871 this.clear_queued = true;
869 this.clear_queued = true;
872 } else {
870 } else {
873
871
874 // Fix the output div's height if the clear_output is waiting for
872 // Fix the output div's height if the clear_output is waiting for
875 // new output (it is being used in an animation).
873 // new output (it is being used in an animation).
876 if (!ignore_que && this.clear_queued) {
874 if (!ignore_que && this.clear_queued) {
877 var height = this.element.height();
875 var height = this.element.height();
878 this.element.height(height);
876 this.element.height(height);
879 this.clear_queued = false;
877 this.clear_queued = false;
880 }
878 }
881
879
882 // Clear all
880 // Clear all
883 // Remove load event handlers from img tags because we don't want
881 // Remove load event handlers from img tags because we don't want
884 // them to fire if the image is never added to the page.
882 // them to fire if the image is never added to the page.
885 this.element.find('img').off('load');
883 this.element.find('img').off('load');
886 this.element.html("");
884 this.element.html("");
887
885
888 // Notify others of changes.
886 // Notify others of changes.
889 this.element.trigger('changed');
887 this.element.trigger('changed');
890
888
891 this.outputs = [];
889 this.outputs = [];
892 this.trusted = true;
890 this.trusted = true;
893 this.unscroll_area();
891 this.unscroll_area();
894 return;
892 return;
895 }
893 }
896 };
894 };
897
895
898
896
899 // JSON serialization
897 // JSON serialization
900
898
901 OutputArea.prototype.fromJSON = function (outputs, metadata) {
899 OutputArea.prototype.fromJSON = function (outputs, metadata) {
902 var len = outputs.length;
900 var len = outputs.length;
903 metadata = metadata || {};
901 metadata = metadata || {};
904
902
905 for (var i=0; i<len; i++) {
903 for (var i=0; i<len; i++) {
906 this.append_output(outputs[i]);
904 this.append_output(outputs[i]);
907 }
905 }
908
906
909 if (metadata.collapsed !== undefined) {
907 if (metadata.collapsed !== undefined) {
910 this.collapsed = metadata.collapsed;
908 this.collapsed = metadata.collapsed;
911 if (metadata.collapsed) {
909 if (metadata.collapsed) {
912 this.collapse_output();
910 this.collapse_output();
913 }
911 }
914 }
912 }
915 if (metadata.autoscroll !== undefined) {
913 if (metadata.autoscroll !== undefined) {
916 this.collapsed = metadata.collapsed;
914 this.collapsed = metadata.collapsed;
917 if (metadata.collapsed) {
915 if (metadata.collapsed) {
918 this.collapse_output();
916 this.collapse_output();
919 } else {
917 } else {
920 this.expand_output();
918 this.expand_output();
921 }
919 }
922 }
920 }
923 };
921 };
924
922
925
923
926 OutputArea.prototype.toJSON = function () {
924 OutputArea.prototype.toJSON = function () {
927 return this.outputs;
925 return this.outputs;
928 };
926 };
929
927
930 /**
928 /**
931 * Class properties
929 * Class properties
932 **/
930 **/
933
931
934 /**
932 /**
935 * Threshold to trigger autoscroll when the OutputArea is resized,
933 * Threshold to trigger autoscroll when the OutputArea is resized,
936 * typically when new outputs are added.
934 * typically when new outputs are added.
937 *
935 *
938 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
936 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
939 * unless it is < 0, in which case autoscroll will never be triggered
937 * unless it is < 0, in which case autoscroll will never be triggered
940 *
938 *
941 * @property auto_scroll_threshold
939 * @property auto_scroll_threshold
942 * @type Number
940 * @type Number
943 * @default 100
941 * @default 100
944 *
942 *
945 **/
943 **/
946 OutputArea.auto_scroll_threshold = 100;
944 OutputArea.auto_scroll_threshold = 100;
947
945
948 /**
946 /**
949 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
947 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
950 * shorter than this are never scrolled.
948 * shorter than this are never scrolled.
951 *
949 *
952 * @property minimum_scroll_threshold
950 * @property minimum_scroll_threshold
953 * @type Number
951 * @type Number
954 * @default 20
952 * @default 20
955 *
953 *
956 **/
954 **/
957 OutputArea.minimum_scroll_threshold = 20;
955 OutputArea.minimum_scroll_threshold = 20;
958
956
959
957
960 OutputArea.display_order = [
958 OutputArea.display_order = [
961 'application/javascript',
959 'application/javascript',
962 'text/html',
960 'text/html',
963 'text/markdown',
961 'text/markdown',
964 'text/latex',
962 'text/latex',
965 'image/svg+xml',
963 'image/svg+xml',
966 'image/png',
964 'image/png',
967 'image/jpeg',
965 'image/jpeg',
968 'application/pdf',
966 'application/pdf',
969 'text/plain'
967 'text/plain'
970 ];
968 ];
971
969
972 OutputArea.append_map = {
970 OutputArea.append_map = {
973 "text/plain" : append_text,
971 "text/plain" : append_text,
974 "text/html" : append_html,
972 "text/html" : append_html,
975 "text/markdown": append_markdown,
973 "text/markdown": append_markdown,
976 "image/svg+xml" : append_svg,
974 "image/svg+xml" : append_svg,
977 "image/png" : append_png,
975 "image/png" : append_png,
978 "image/jpeg" : append_jpeg,
976 "image/jpeg" : append_jpeg,
979 "text/latex" : append_latex,
977 "text/latex" : append_latex,
980 "application/javascript" : append_javascript,
978 "application/javascript" : append_javascript,
981 "application/pdf" : append_pdf
979 "application/pdf" : append_pdf
982 };
980 };
983
981
984 // For backwards compatability.
982 // For backwards compatability.
985 IPython.OutputArea = OutputArea;
983 IPython.OutputArea = OutputArea;
986
984
987 return {'OutputArea': OutputArea};
985 return {'OutputArea': OutputArea};
988 });
986 });
@@ -1,79 +1,80 b''
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 define([
4 define([
5 "widgets/js/widget",
5 "widgets/js/widget",
6 "jquery",
6 "jquery",
7 'notebook/js/outputarea',
7 'notebook/js/outputarea',
8 ], function(widget, $, outputarea) {
8 ], function(widget, $, outputarea) {
9 'use strict';
9 'use strict';
10
10
11 var OutputView = widget.DOMWidgetView.extend({
11 var OutputView = widget.DOMWidgetView.extend({
12 initialize: function (parameters) {
13 /**
12 /**
14 * Public constructor
13 * Public constructor
15 */
14 */
15 initialize: function (parameters) {
16 OutputView.__super__.initialize.apply(this, [parameters]);
16 OutputView.__super__.initialize.apply(this, [parameters]);
17 this.model.on('msg:custom', this._handle_route_msg, this);
17 this.model.on('msg:custom', this._handle_route_msg, this);
18 },
18 },
19
19
20 render: function(){
21 /**
20 /**
22 * Called when view is rendered.
21 * Called when view is rendered.
23 */
22 */
23 render: function(){
24 this.output_area = new outputarea.OutputArea({
24 this.output_area = new outputarea.OutputArea({
25 selector: this.$el,
25 selector: this.$el,
26 prompt_area: false,
26 prompt_area: false,
27 events: this.model.widget_manager.notebook.events,
27 events: this.model.widget_manager.notebook.events,
28 keyboard_manager: this.model.widget_manager.keyboard_manager });
28 keyboard_manager: this.model.widget_manager.keyboard_manager });
29
29
30 // Make output area reactive.
30 // Make output area reactive.
31 var that = this;
31 var that = this;
32 this.output_area.element.on('changed', function() {
32 this.output_area.element.on('changed', function() {
33 that.model.set('contents', that.output_area.element.html());
33 that.model.set('contents', that.output_area.element.html());
34 });
34 });
35 this.model.on('change:contents', function(){
35 this.model.on('change:contents', function(){
36 var html = this.model.get('contents');
36 var html = this.model.get('contents');
37 if (this.output_area.element.html() != html) {
37 if (this.output_area.element.html() != html) {
38 this.output_area.element.html(html);
38 this.output_area.element.html(html);
39 }
39 }
40 }, this);
40 }, this);
41
41
42 // Set initial contents.
42 // Set initial contents.
43 this.output_area.element.html(this.model.get('contents'));
43 this.output_area.element.html(this.model.get('contents'));
44 },
44 },
45
45
46 /**
47 * Handles re-routed iopub messages.
48 */
46 _handle_route_msg: function(content) {
49 _handle_route_msg: function(content) {
47 if (content) {
50 if (content) {
48 // return {
51 var msg_type = content.type;
49 // shell : {
52 var json = {
50 // reply : $.proxy(this._handle_execute_reply, this),
53 output_type: msg_type
51 // payload : {
54 };
52 // set_next_input : $.proxy(this._handle_set_next_input, this),
55
53 // page : $.proxy(this._open_with_pager, this)
56 var data = content.args[0];
54 // }
57 if (msg_type=='clear_output') {
55 // },
58 this.output_area.clear_output(data.wait || false);
56 // iopub : {
59 return;
57 // output : function() {
60 } else if (msg_type === "stream") {
58 // that.output_area.handle_output.apply(that.output_area, arguments);
61 data = content.kwargs.content;
59 // },
62 json.text = data.text;
60 // clear_output : function() {
63 json.name = data.name;
61 // that.output_area.handle_clear_output.apply(that.output_area, arguments);
64 } else if (msg_type === "display_data") {
62 // },
65 json.data = data.data;
63 // },
66 json.metadata = data.metadata;
64 // input : $.proxy(this._handle_input_request, this)
67 } else {
65 // };
68 console.log("unhandled output message", msg);
66 // };
69 return;
67 if (content.method == 'push') {
68 cell.push_output_area(this.output_area);
69 } else if (content.method == 'pop') {
70 cell.pop_output_area(this.output_area);
71 }
70 }
71
72 this.output_area.append_output(json);
72 }
73 }
73 },
74 },
74 });
75 });
75
76
76 return {
77 return {
77 'OutputView': OutputView,
78 'OutputView': OutputView,
78 };
79 };
79 });
80 });
@@ -1,50 +1,67 b''
1 """Output class.
1 """Output class.
2
2
3 Represents a widget that can be used to display output within the widget area.
3 Represents a widget that can be used to display output within the widget area.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 from .widget import DOMWidget
9 from .widget import DOMWidget
10 import sys
10 import sys
11 from IPython.utils.traitlets import Unicode, List
11 from IPython.utils.traitlets import Unicode, List
12 from IPython.display import clear_output
12 from IPython.display import clear_output
13 from IPython.testing.skipdoctest import skip_doctest
13 from IPython.testing.skipdoctest import skip_doctest
14
14
15 @skip_doctest
15 @skip_doctest
16 class Output(DOMWidget):
16 class Output(DOMWidget):
17 """Widget used as a context manager to display output.
17 """Widget used as a context manager to display output.
18
18
19 This widget can capture and display stdout, stderr, and rich output. To use
19 This widget can capture and display stdout, stderr, and rich output. To use
20 it, create an instance of it and display it. Then use it as a context
20 it, create an instance of it and display it. Then use it as a context
21 manager. Any output produced while in it's context will be captured and
21 manager. Any output produced while in it's context will be captured and
22 displayed in it instead of the standard output area.
22 displayed in it instead of the standard output area.
23
23
24 Example
24 Example
25 from IPython.html import widgets
25 from IPython.html import widgets
26 from IPython.display import display
26 from IPython.display import display
27 out = widgets.Output()
27 out = widgets.Output()
28 display(out)
28 display(out)
29
29
30 print('prints to output area')
30 print('prints to output area')
31
31
32 with out:
32 with out:
33 print('prints to output widget')"""
33 print('prints to output widget')"""
34 _view_name = Unicode('OutputView', sync=True)
34 _view_name = Unicode('OutputView', sync=True)
35
35
36 def clear_output(self, *pargs, **kwargs):
36 def clear_output(self, *pargs, **kwargs):
37 with self:
37 with self:
38 clear_output(*pargs, **kwargs)
38 clear_output(*pargs, **kwargs)
39
39
40 def __enter__(self):
40 def __enter__(self):
41 """Called upon entering output widget context manager."""
41 self._flush()
42 self._flush()
42 self.send({'method': 'push'})
43 kernel = get_ipython().kernel
44 session = kernel.session
45 send = session.send
46 self._original_send = send
47 self._session = session
48
49 def send_hook(stream, msg_or_type, *args, **kwargs):
50 if stream is kernel.iopub_socket and msg_or_type in ['clear_output', 'stream', 'display_data']:
51 msg = {'type': msg_or_type, 'args': args, 'kwargs': kwargs}
52 self.send(msg)
53 else:
54 send(stream, msg_or_type, *args, **kwargs)
55 return
56
57 session.send = send_hook
43
58
44 def __exit__(self, exception_type, exception_value, traceback):
59 def __exit__(self, exception_type, exception_value, traceback):
60 """Called upon exiting output widget context manager."""
45 self._flush()
61 self._flush()
46 self.send({'method': 'pop'})
62 self._session.send = self._original_send
47
63
48 def _flush(self):
64 def _flush(self):
65 """Flush stdout and stderr buffers."""
49 sys.stdout.flush()
66 sys.stdout.flush()
50 sys.stderr.flush()
67 sys.stderr.flush()
General Comments 0
You need to be logged in to leave comments. Login now