##// END OF EJS Templates
Merge pull request #7523 from jdfreder/outputwidgetfix...
Min RK -
r20096:5616d61c merge
parent child Browse files
Show More
@@ -1,677 +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 // Keep a stack of the 'active' output areas (where active means the
101 // output area that recieves output). When a user activates an output
102 // area, it gets pushed to the stack. Then, when the output area is
103 // deactivated, it's popped from the stack. When the stack is empty,
104 // the cell's output area is used.
105 this.active_output_areas = [];
106 var that = this;
107 Object.defineProperty(this, 'active_output_area', {
108 get: function() {
109 if (that.active_output_areas && that.active_output_areas.length > 0) {
110 return that.active_output_areas[that.active_output_areas.length-1];
111 } else {
112 return that.output_area;
113 }
114 },
115 });
116
100
117 this.last_msg_id = null;
101 this.last_msg_id = null;
118 this.completer = null;
102 this.completer = null;
119 this.widget_views = [];
103 this.widget_views = [];
120 this._widgets_live = true;
104 this._widgets_live = true;
121
105
122 Cell.apply(this,[{
106 Cell.apply(this,[{
123 config: $.extend({}, CodeCell.options_default),
107 config: $.extend({}, CodeCell.options_default),
124 keyboard_manager: options.keyboard_manager,
108 keyboard_manager: options.keyboard_manager,
125 events: this.events}]);
109 events: this.events}]);
126
110
127 // Attributes we want to override in this subclass.
111 // Attributes we want to override in this subclass.
128 this.cell_type = "code";
112 this.cell_type = "code";
113 var that = this;
129 this.element.focusout(
114 this.element.focusout(
130 function() { that.auto_highlight(); }
115 function() { that.auto_highlight(); }
131 );
116 );
132 };
117 };
133
118
134 CodeCell.options_default = {
119 CodeCell.options_default = {
135 cm_config : {
120 cm_config : {
136 extraKeys: {
121 extraKeys: {
137 "Tab" : "indentMore",
122 "Tab" : "indentMore",
138 "Shift-Tab" : "indentLess",
123 "Shift-Tab" : "indentLess",
139 "Backspace" : "delSpaceToPrevTabStop",
124 "Backspace" : "delSpaceToPrevTabStop",
140 "Cmd-/" : "toggleComment",
125 "Cmd-/" : "toggleComment",
141 "Ctrl-/" : "toggleComment"
126 "Ctrl-/" : "toggleComment"
142 },
127 },
143 mode: 'ipython',
128 mode: 'ipython',
144 theme: 'ipython',
129 theme: 'ipython',
145 matchBrackets: true
130 matchBrackets: true
146 }
131 }
147 };
132 };
148
133
149 CodeCell.config_defaults = {
134 CodeCell.config_defaults = {
150 highlight_modes : {
135 highlight_modes : {
151 'magic_javascript' :{'reg':[/^%%javascript/]},
136 'magic_javascript' :{'reg':[/^%%javascript/]},
152 'magic_perl' :{'reg':[/^%%perl/]},
137 'magic_perl' :{'reg':[/^%%perl/]},
153 'magic_ruby' :{'reg':[/^%%ruby/]},
138 'magic_ruby' :{'reg':[/^%%ruby/]},
154 'magic_python' :{'reg':[/^%%python3?/]},
139 'magic_python' :{'reg':[/^%%python3?/]},
155 'magic_shell' :{'reg':[/^%%bash/]},
140 'magic_shell' :{'reg':[/^%%bash/]},
156 'magic_r' :{'reg':[/^%%R/]},
141 'magic_r' :{'reg':[/^%%R/]},
157 'magic_text/x-cython' :{'reg':[/^%%cython/]},
142 'magic_text/x-cython' :{'reg':[/^%%cython/]},
158 },
143 },
159 };
144 };
160
145
161 CodeCell.msg_cells = {};
146 CodeCell.msg_cells = {};
162
147
163 CodeCell.prototype = Object.create(Cell.prototype);
148 CodeCell.prototype = Object.create(Cell.prototype);
164
149
165 /**
166 * @method push_output_area
167 */
168 CodeCell.prototype.push_output_area = function (output_area) {
169 this.active_output_areas.push(output_area);
170 };
171
172 /**
173 * @method pop_output_area
174 */
175 CodeCell.prototype.pop_output_area = function (output_area) {
176 var index = this.active_output_areas.lastIndexOf(output_area);
177 if (index > -1) {
178 this.active_output_areas.splice(index, 1);
179 }
180 };
181
182 /** @method create_element */
150 /** @method create_element */
183 CodeCell.prototype.create_element = function () {
151 CodeCell.prototype.create_element = function () {
184 Cell.prototype.create_element.apply(this, arguments);
152 Cell.prototype.create_element.apply(this, arguments);
185
153
186 var cell = $('<div></div>').addClass('cell code_cell');
154 var cell = $('<div></div>').addClass('cell code_cell');
187 cell.attr('tabindex','2');
155 cell.attr('tabindex','2');
188
156
189 var input = $('<div></div>').addClass('input');
157 var input = $('<div></div>').addClass('input');
190 var prompt = $('<div/>').addClass('prompt input_prompt');
158 var prompt = $('<div/>').addClass('prompt input_prompt');
191 var inner_cell = $('<div/>').addClass('inner_cell');
159 var inner_cell = $('<div/>').addClass('inner_cell');
192 this.celltoolbar = new celltoolbar.CellToolbar({
160 this.celltoolbar = new celltoolbar.CellToolbar({
193 cell: this,
161 cell: this,
194 notebook: this.notebook});
162 notebook: this.notebook});
195 inner_cell.append(this.celltoolbar.element);
163 inner_cell.append(this.celltoolbar.element);
196 var input_area = $('<div/>').addClass('input_area');
164 var input_area = $('<div/>').addClass('input_area');
197 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);
198 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
166 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
199 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
167 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
200 inner_cell.append(input_area);
168 inner_cell.append(input_area);
201 input.append(prompt).append(inner_cell);
169 input.append(prompt).append(inner_cell);
202
170
203 var widget_area = $('<div/>')
171 var widget_area = $('<div/>')
204 .addClass('widget-area')
172 .addClass('widget-area')
205 .hide();
173 .hide();
206 this.widget_area = widget_area;
174 this.widget_area = widget_area;
207 var widget_prompt = $('<div/>')
175 var widget_prompt = $('<div/>')
208 .addClass('prompt')
176 .addClass('prompt')
209 .appendTo(widget_area);
177 .appendTo(widget_area);
210 var widget_subarea = $('<div/>')
178 var widget_subarea = $('<div/>')
211 .addClass('widget-subarea')
179 .addClass('widget-subarea')
212 .appendTo(widget_area);
180 .appendTo(widget_area);
213 this.widget_subarea = widget_subarea;
181 this.widget_subarea = widget_subarea;
214 var that = this;
182 var that = this;
215 var widget_clear_buton = $('<button />')
183 var widget_clear_buton = $('<button />')
216 .addClass('close')
184 .addClass('close')
217 .html('&times;')
185 .html('&times;')
218 .click(function() {
186 .click(function() {
219 widget_area.slideUp('', function(){
187 widget_area.slideUp('', function(){
220 for (var i = 0; i < that.widget_views.length; i++) {
188 for (var i = 0; i < that.widget_views.length; i++) {
221 var view = that.widget_views[i];
189 var view = that.widget_views[i];
222 view.remove();
190 view.remove();
223
191
224 // Remove widget live events.
192 // Remove widget live events.
225 view.off('comm:live', that._widget_live);
193 view.off('comm:live', that._widget_live);
226 view.off('comm:dead', that._widget_dead);
194 view.off('comm:dead', that._widget_dead);
227 }
195 }
228 that.widget_views = [];
196 that.widget_views = [];
229 widget_subarea.html('');
197 widget_subarea.html('');
230 });
198 });
231 })
199 })
232 .appendTo(widget_prompt);
200 .appendTo(widget_prompt);
233
201
234 var output = $('<div></div>');
202 var output = $('<div></div>');
235 cell.append(input).append(widget_area).append(output);
203 cell.append(input).append(widget_area).append(output);
236 this.element = cell;
204 this.element = cell;
237 this.output_area = new outputarea.OutputArea({
205 this.output_area = new outputarea.OutputArea({
238 selector: output,
206 selector: output,
239 prompt_area: true,
207 prompt_area: true,
240 events: this.events,
208 events: this.events,
241 keyboard_manager: this.keyboard_manager});
209 keyboard_manager: this.keyboard_manager});
242 this.completer = new completer.Completer(this, this.events);
210 this.completer = new completer.Completer(this, this.events);
243 };
211 };
244
212
245 /**
213 /**
246 * Display a widget view in the cell.
214 * Display a widget view in the cell.
247 */
215 */
248 CodeCell.prototype.display_widget_view = function(view_promise) {
216 CodeCell.prototype.display_widget_view = function(view_promise) {
249
217
250 // Display a dummy element
218 // Display a dummy element
251 var dummy = $('<div/>');
219 var dummy = $('<div/>');
252 this.widget_subarea.append(dummy);
220 this.widget_subarea.append(dummy);
253
221
254 // Display the view.
222 // Display the view.
255 var that = this;
223 var that = this;
256 return view_promise.then(function(view) {
224 return view_promise.then(function(view) {
257 that.widget_area.show();
225 that.widget_area.show();
258 dummy.replaceWith(view.$el);
226 dummy.replaceWith(view.$el);
259 that.widget_views.push(view);
227 that.widget_views.push(view);
260
228
261 // Check the live state of the view's model.
229 // Check the live state of the view's model.
262 if (view.model.comm_live) {
230 if (view.model.comm_live) {
263 that._widget_live(view);
231 that._widget_live(view);
264 } else {
232 } else {
265 that._widget_dead(view);
233 that._widget_dead(view);
266 }
234 }
267
235
268 // Listen to comm live events for the view.
236 // Listen to comm live events for the view.
269 view.on('comm:live', that._widget_live, that);
237 view.on('comm:live', that._widget_live, that);
270 view.on('comm:dead', that._widget_dead, that);
238 view.on('comm:dead', that._widget_dead, that);
271 return view;
239 return view;
272 });
240 });
273 };
241 };
274
242
275 /**
243 /**
276 * Handles when a widget loses it's comm connection.
244 * Handles when a widget loses it's comm connection.
277 * @param {WidgetView} view
245 * @param {WidgetView} view
278 */
246 */
279 CodeCell.prototype._widget_dead = function(view) {
247 CodeCell.prototype._widget_dead = function(view) {
280 if (this._widgets_live) {
248 if (this._widgets_live) {
281 this._widgets_live = false;
249 this._widgets_live = false;
282 this.widget_area.addClass('connection-problems');
250 this.widget_area.addClass('connection-problems');
283 }
251 }
284
252
285 };
253 };
286
254
287 /**
255 /**
288 * Handles when a widget is connected to a live comm.
256 * Handles when a widget is connected to a live comm.
289 * @param {WidgetView} view
257 * @param {WidgetView} view
290 */
258 */
291 CodeCell.prototype._widget_live = function(view) {
259 CodeCell.prototype._widget_live = function(view) {
292 if (!this._widgets_live) {
260 if (!this._widgets_live) {
293 // Check that the other widgets are live too. O(N) operation.
261 // Check that the other widgets are live too. O(N) operation.
294 // Abort the function at the first dead widget found.
262 // Abort the function at the first dead widget found.
295 for (var i = 0; i < this.widget_views.length; i++) {
263 for (var i = 0; i < this.widget_views.length; i++) {
296 if (!this.widget_views[i].model.comm_live) return;
264 if (!this.widget_views[i].model.comm_live) return;
297 }
265 }
298 this._widgets_live = true;
266 this._widgets_live = true;
299 this.widget_area.removeClass('connection-problems');
267 this.widget_area.removeClass('connection-problems');
300 }
268 }
301 };
269 };
302
270
303 /** @method bind_events */
271 /** @method bind_events */
304 CodeCell.prototype.bind_events = function () {
272 CodeCell.prototype.bind_events = function () {
305 Cell.prototype.bind_events.apply(this);
273 Cell.prototype.bind_events.apply(this);
306 var that = this;
274 var that = this;
307
275
308 this.element.focusout(
276 this.element.focusout(
309 function() { that.auto_highlight(); }
277 function() { that.auto_highlight(); }
310 );
278 );
311 };
279 };
312
280
313
281
314 /**
282 /**
315 * This method gets called in CodeMirror's onKeyDown/onKeyPress
283 * This method gets called in CodeMirror's onKeyDown/onKeyPress
316 * handlers and is used to provide custom key handling. Its return
284 * handlers and is used to provide custom key handling. Its return
317 * value is used to determine if CodeMirror should ignore the event:
285 * value is used to determine if CodeMirror should ignore the event:
318 * true = ignore, false = don't ignore.
286 * true = ignore, false = don't ignore.
319 * @method handle_codemirror_keyevent
287 * @method handle_codemirror_keyevent
320 */
288 */
321
289
322 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
290 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
323
291
324 var that = this;
292 var that = this;
325 // whatever key is pressed, first, cancel the tooltip request before
293 // whatever key is pressed, first, cancel the tooltip request before
326 // 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
327 var tooltip_closed = null;
295 var tooltip_closed = null;
328 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
296 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
329 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
297 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
330 }
298 }
331
299
332 var cur = editor.getCursor();
300 var cur = editor.getCursor();
333 if (event.keyCode === keycodes.enter){
301 if (event.keyCode === keycodes.enter){
334 this.auto_highlight();
302 this.auto_highlight();
335 }
303 }
336
304
337 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) {
338 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
306 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
339 // browser and keyboard layout !
307 // browser and keyboard layout !
340 // Pressing '(' , request tooltip, don't forget to reappend it
308 // Pressing '(' , request tooltip, don't forget to reappend it
341 // The second argument says to hide the tooltip if the docstring
309 // The second argument says to hide the tooltip if the docstring
342 // is actually empty
310 // is actually empty
343 this.tooltip.pending(that, true);
311 this.tooltip.pending(that, true);
344 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
312 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
345 // If tooltip is active, cancel it. The call to
313 // If tooltip is active, cancel it. The call to
346 // remove_and_cancel_tooltip above doesn't pass, force=true.
314 // remove_and_cancel_tooltip above doesn't pass, force=true.
347 // Because of this it won't actually close the tooltip
315 // Because of this it won't actually close the tooltip
348 // 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
349 // and close it with force=true.
317 // and close it with force=true.
350 if (!this.tooltip._hidden) {
318 if (!this.tooltip._hidden) {
351 this.tooltip.remove_and_cancel_tooltip(true);
319 this.tooltip.remove_and_cancel_tooltip(true);
352 }
320 }
353 // 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
354 // handle this event.
322 // handle this event.
355 event.codemirrorIgnore = true;
323 event.codemirrorIgnore = true;
356 event.preventDefault();
324 event.preventDefault();
357 return true;
325 return true;
358 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
326 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
359 if (editor.somethingSelected() || editor.getSelections().length !== 1){
327 if (editor.somethingSelected() || editor.getSelections().length !== 1){
360 var anchor = editor.getCursor("anchor");
328 var anchor = editor.getCursor("anchor");
361 var head = editor.getCursor("head");
329 var head = editor.getCursor("head");
362 if( anchor.line !== head.line){
330 if( anchor.line !== head.line){
363 return false;
331 return false;
364 }
332 }
365 }
333 }
366 this.tooltip.request(that);
334 this.tooltip.request(that);
367 event.codemirrorIgnore = true;
335 event.codemirrorIgnore = true;
368 event.preventDefault();
336 event.preventDefault();
369 return true;
337 return true;
370 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
338 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
371 // Tab completion.
339 // Tab completion.
372 this.tooltip.remove_and_cancel_tooltip();
340 this.tooltip.remove_and_cancel_tooltip();
373
341
374 // 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
375 if (editor.somethingSelected() || editor.getSelections().length > 1) {
343 if (editor.somethingSelected() || editor.getSelections().length > 1) {
376 return false;
344 return false;
377 }
345 }
378 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
346 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
379 if (pre_cursor.trim() === "") {
347 if (pre_cursor.trim() === "") {
380 // 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
381 // is empty. In this case, let CodeMirror handle indentation.
349 // is empty. In this case, let CodeMirror handle indentation.
382 return false;
350 return false;
383 } else {
351 } else {
384 event.codemirrorIgnore = true;
352 event.codemirrorIgnore = true;
385 event.preventDefault();
353 event.preventDefault();
386 this.completer.startCompletion();
354 this.completer.startCompletion();
387 return true;
355 return true;
388 }
356 }
389 }
357 }
390
358
391 // 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
392 // 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)
393 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
361 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
394 };
362 };
395
363
396 // Kernel related calls.
364 // Kernel related calls.
397
365
398 CodeCell.prototype.set_kernel = function (kernel) {
366 CodeCell.prototype.set_kernel = function (kernel) {
399 this.kernel = kernel;
367 this.kernel = kernel;
400 };
368 };
401
369
402 /**
370 /**
403 * Execute current code cell to the kernel
371 * Execute current code cell to the kernel
404 * @method execute
372 * @method execute
405 */
373 */
406 CodeCell.prototype.execute = function (stop_on_error) {
374 CodeCell.prototype.execute = function (stop_on_error) {
407 if (!this.kernel || !this.kernel.is_connected()) {
375 if (!this.kernel || !this.kernel.is_connected()) {
408 console.log("Can't execute, kernel is not connected.");
376 console.log("Can't execute, kernel is not connected.");
409 return;
377 return;
410 }
378 }
411
379
412 this.active_output_area.clear_output(false, true);
380 this.output_area.clear_output(false, true);
413
381
414 if (stop_on_error === undefined) {
382 if (stop_on_error === undefined) {
415 stop_on_error = true;
383 stop_on_error = true;
416 }
384 }
417
385
418 // Clear widget area
386 // Clear widget area
419 for (var i = 0; i < this.widget_views.length; i++) {
387 for (var i = 0; i < this.widget_views.length; i++) {
420 var view = this.widget_views[i];
388 var view = this.widget_views[i];
421 view.remove();
389 view.remove();
422
390
423 // Remove widget live events.
391 // Remove widget live events.
424 view.off('comm:live', this._widget_live);
392 view.off('comm:live', this._widget_live);
425 view.off('comm:dead', this._widget_dead);
393 view.off('comm:dead', this._widget_dead);
426 }
394 }
427 this.widget_views = [];
395 this.widget_views = [];
428 this.widget_subarea.html('');
396 this.widget_subarea.html('');
429 this.widget_subarea.height('');
397 this.widget_subarea.height('');
430 this.widget_area.height('');
398 this.widget_area.height('');
431 this.widget_area.hide();
399 this.widget_area.hide();
432
400
433 this.set_input_prompt('*');
401 this.set_input_prompt('*');
434 this.element.addClass("running");
402 this.element.addClass("running");
435 if (this.last_msg_id) {
403 if (this.last_msg_id) {
436 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
404 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
437 }
405 }
438 var callbacks = this.get_callbacks();
406 var callbacks = this.get_callbacks();
439
407
440 var old_msg_id = this.last_msg_id;
408 var old_msg_id = this.last_msg_id;
441 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,
442 stop_on_error : stop_on_error});
410 stop_on_error : stop_on_error});
443 if (old_msg_id) {
411 if (old_msg_id) {
444 delete CodeCell.msg_cells[old_msg_id];
412 delete CodeCell.msg_cells[old_msg_id];
445 }
413 }
446 CodeCell.msg_cells[this.last_msg_id] = this;
414 CodeCell.msg_cells[this.last_msg_id] = this;
447 this.render();
415 this.render();
448 this.events.trigger('execute.CodeCell', {cell: this});
416 this.events.trigger('execute.CodeCell', {cell: this});
449 };
417 };
450
418
451 /**
419 /**
452 * Construct the default callbacks for
420 * Construct the default callbacks for
453 * @method get_callbacks
421 * @method get_callbacks
454 */
422 */
455 CodeCell.prototype.get_callbacks = function () {
423 CodeCell.prototype.get_callbacks = function () {
456 var that = this;
424 var that = this;
457 return {
425 return {
458 shell : {
426 shell : {
459 reply : $.proxy(this._handle_execute_reply, this),
427 reply : $.proxy(this._handle_execute_reply, this),
460 payload : {
428 payload : {
461 set_next_input : $.proxy(this._handle_set_next_input, this),
429 set_next_input : $.proxy(this._handle_set_next_input, this),
462 page : $.proxy(this._open_with_pager, this)
430 page : $.proxy(this._open_with_pager, this)
463 }
431 }
464 },
432 },
465 iopub : {
433 iopub : {
466 output : function() {
434 output : function() {
467 that.active_output_area.handle_output.apply(that.active_output_area, arguments);
435 that.output_area.handle_output.apply(that.output_area, arguments);
468 },
436 },
469 clear_output : function() {
437 clear_output : function() {
470 that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments);
438 that.output_area.handle_clear_output.apply(that.output_area, arguments);
471 },
439 },
472 },
440 },
473 input : $.proxy(this._handle_input_request, this)
441 input : $.proxy(this._handle_input_request, this)
474 };
442 };
475 };
443 };
476
444
477 CodeCell.prototype._open_with_pager = function (payload) {
445 CodeCell.prototype._open_with_pager = function (payload) {
478 this.events.trigger('open_with_text.Pager', payload);
446 this.events.trigger('open_with_text.Pager', payload);
479 };
447 };
480
448
481 /**
449 /**
482 * @method _handle_execute_reply
450 * @method _handle_execute_reply
483 * @private
451 * @private
484 */
452 */
485 CodeCell.prototype._handle_execute_reply = function (msg) {
453 CodeCell.prototype._handle_execute_reply = function (msg) {
486 this.set_input_prompt(msg.content.execution_count);
454 this.set_input_prompt(msg.content.execution_count);
487 this.element.removeClass("running");
455 this.element.removeClass("running");
488 this.events.trigger('set_dirty.Notebook', {value: true});
456 this.events.trigger('set_dirty.Notebook', {value: true});
489 };
457 };
490
458
491 /**
459 /**
492 * @method _handle_set_next_input
460 * @method _handle_set_next_input
493 * @private
461 * @private
494 */
462 */
495 CodeCell.prototype._handle_set_next_input = function (payload) {
463 CodeCell.prototype._handle_set_next_input = function (payload) {
496 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
464 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
497 this.events.trigger('set_next_input.Notebook', data);
465 this.events.trigger('set_next_input.Notebook', data);
498 };
466 };
499
467
500 /**
468 /**
501 * @method _handle_input_request
469 * @method _handle_input_request
502 * @private
470 * @private
503 */
471 */
504 CodeCell.prototype._handle_input_request = function (msg) {
472 CodeCell.prototype._handle_input_request = function (msg) {
505 this.active_output_area.append_raw_input(msg);
473 this.output_area.append_raw_input(msg);
506 };
474 };
507
475
508
476
509 // Basic cell manipulation.
477 // Basic cell manipulation.
510
478
511 CodeCell.prototype.select = function () {
479 CodeCell.prototype.select = function () {
512 var cont = Cell.prototype.select.apply(this);
480 var cont = Cell.prototype.select.apply(this);
513 if (cont) {
481 if (cont) {
514 this.code_mirror.refresh();
482 this.code_mirror.refresh();
515 this.auto_highlight();
483 this.auto_highlight();
516 }
484 }
517 return cont;
485 return cont;
518 };
486 };
519
487
520 CodeCell.prototype.render = function () {
488 CodeCell.prototype.render = function () {
521 var cont = Cell.prototype.render.apply(this);
489 var cont = Cell.prototype.render.apply(this);
522 // Always execute, even if we are already in the rendered state
490 // Always execute, even if we are already in the rendered state
523 return cont;
491 return cont;
524 };
492 };
525
493
526 CodeCell.prototype.select_all = function () {
494 CodeCell.prototype.select_all = function () {
527 var start = {line: 0, ch: 0};
495 var start = {line: 0, ch: 0};
528 var nlines = this.code_mirror.lineCount();
496 var nlines = this.code_mirror.lineCount();
529 var last_line = this.code_mirror.getLine(nlines-1);
497 var last_line = this.code_mirror.getLine(nlines-1);
530 var end = {line: nlines-1, ch: last_line.length};
498 var end = {line: nlines-1, ch: last_line.length};
531 this.code_mirror.setSelection(start, end);
499 this.code_mirror.setSelection(start, end);
532 };
500 };
533
501
534
502
535 CodeCell.prototype.collapse_output = function () {
503 CodeCell.prototype.collapse_output = function () {
536 this.output_area.collapse();
504 this.output_area.collapse();
537 };
505 };
538
506
539
507
540 CodeCell.prototype.expand_output = function () {
508 CodeCell.prototype.expand_output = function () {
541 this.output_area.expand();
509 this.output_area.expand();
542 this.output_area.unscroll_area();
510 this.output_area.unscroll_area();
543 };
511 };
544
512
545 CodeCell.prototype.scroll_output = function () {
513 CodeCell.prototype.scroll_output = function () {
546 this.output_area.expand();
514 this.output_area.expand();
547 this.output_area.scroll_if_long();
515 this.output_area.scroll_if_long();
548 };
516 };
549
517
550 CodeCell.prototype.toggle_output = function () {
518 CodeCell.prototype.toggle_output = function () {
551 this.output_area.toggle_output();
519 this.output_area.toggle_output();
552 };
520 };
553
521
554 CodeCell.prototype.toggle_output_scroll = function () {
522 CodeCell.prototype.toggle_output_scroll = function () {
555 this.output_area.toggle_scroll();
523 this.output_area.toggle_scroll();
556 };
524 };
557
525
558
526
559 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
527 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
560 var ns;
528 var ns;
561 if (prompt_value === undefined || prompt_value === null) {
529 if (prompt_value === undefined || prompt_value === null) {
562 ns = "&nbsp;";
530 ns = "&nbsp;";
563 } else {
531 } else {
564 ns = encodeURIComponent(prompt_value);
532 ns = encodeURIComponent(prompt_value);
565 }
533 }
566 return 'In&nbsp;[' + ns + ']:';
534 return 'In&nbsp;[' + ns + ']:';
567 };
535 };
568
536
569 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
537 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
570 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
538 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
571 for(var i=1; i < lines_number; i++) {
539 for(var i=1; i < lines_number; i++) {
572 html.push(['...:']);
540 html.push(['...:']);
573 }
541 }
574 return html.join('<br/>');
542 return html.join('<br/>');
575 };
543 };
576
544
577 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
545 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
578
546
579
547
580 CodeCell.prototype.set_input_prompt = function (number) {
548 CodeCell.prototype.set_input_prompt = function (number) {
581 var nline = 1;
549 var nline = 1;
582 if (this.code_mirror !== undefined) {
550 if (this.code_mirror !== undefined) {
583 nline = this.code_mirror.lineCount();
551 nline = this.code_mirror.lineCount();
584 }
552 }
585 this.input_prompt_number = number;
553 this.input_prompt_number = number;
586 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);
587 // This HTML call is okay because the user contents are escaped.
555 // This HTML call is okay because the user contents are escaped.
588 this.element.find('div.input_prompt').html(prompt_html);
556 this.element.find('div.input_prompt').html(prompt_html);
589 };
557 };
590
558
591
559
592 CodeCell.prototype.clear_input = function () {
560 CodeCell.prototype.clear_input = function () {
593 this.code_mirror.setValue('');
561 this.code_mirror.setValue('');
594 };
562 };
595
563
596
564
597 CodeCell.prototype.get_text = function () {
565 CodeCell.prototype.get_text = function () {
598 return this.code_mirror.getValue();
566 return this.code_mirror.getValue();
599 };
567 };
600
568
601
569
602 CodeCell.prototype.set_text = function (code) {
570 CodeCell.prototype.set_text = function (code) {
603 return this.code_mirror.setValue(code);
571 return this.code_mirror.setValue(code);
604 };
572 };
605
573
606
574
607 CodeCell.prototype.clear_output = function (wait) {
575 CodeCell.prototype.clear_output = function (wait) {
608 this.active_output_area.clear_output(wait);
576 this.output_area.clear_output(wait);
609 this.set_input_prompt();
577 this.set_input_prompt();
610 };
578 };
611
579
612
580
613 // JSON serialization
581 // JSON serialization
614
582
615 CodeCell.prototype.fromJSON = function (data) {
583 CodeCell.prototype.fromJSON = function (data) {
616 Cell.prototype.fromJSON.apply(this, arguments);
584 Cell.prototype.fromJSON.apply(this, arguments);
617 if (data.cell_type === 'code') {
585 if (data.cell_type === 'code') {
618 if (data.source !== undefined) {
586 if (data.source !== undefined) {
619 this.set_text(data.source);
587 this.set_text(data.source);
620 // 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
621 // to this state, instead of a blank cell
589 // to this state, instead of a blank cell
622 this.code_mirror.clearHistory();
590 this.code_mirror.clearHistory();
623 this.auto_highlight();
591 this.auto_highlight();
624 }
592 }
625 this.set_input_prompt(data.execution_count);
593 this.set_input_prompt(data.execution_count);
626 this.output_area.trusted = data.metadata.trusted || false;
594 this.output_area.trusted = data.metadata.trusted || false;
627 this.output_area.fromJSON(data.outputs);
595 this.output_area.fromJSON(data.outputs);
628 if (data.metadata.collapsed !== undefined) {
596 if (data.metadata.collapsed !== undefined) {
629 if (data.metadata.collapsed) {
597 if (data.metadata.collapsed) {
630 this.collapse_output();
598 this.collapse_output();
631 } else {
599 } else {
632 this.expand_output();
600 this.expand_output();
633 }
601 }
634 }
602 }
635 }
603 }
636 };
604 };
637
605
638
606
639 CodeCell.prototype.toJSON = function () {
607 CodeCell.prototype.toJSON = function () {
640 var data = Cell.prototype.toJSON.apply(this);
608 var data = Cell.prototype.toJSON.apply(this);
641 data.source = this.get_text();
609 data.source = this.get_text();
642 // is finite protect against undefined and '*' value
610 // is finite protect against undefined and '*' value
643 if (isFinite(this.input_prompt_number)) {
611 if (isFinite(this.input_prompt_number)) {
644 data.execution_count = this.input_prompt_number;
612 data.execution_count = this.input_prompt_number;
645 } else {
613 } else {
646 data.execution_count = null;
614 data.execution_count = null;
647 }
615 }
648 var outputs = this.output_area.toJSON();
616 var outputs = this.output_area.toJSON();
649 data.outputs = outputs;
617 data.outputs = outputs;
650 data.metadata.trusted = this.output_area.trusted;
618 data.metadata.trusted = this.output_area.trusted;
651 data.metadata.collapsed = this.output_area.collapsed;
619 data.metadata.collapsed = this.output_area.collapsed;
652 return data;
620 return data;
653 };
621 };
654
622
655 /**
623 /**
656 * handle cell level logic when a cell is unselected
624 * handle cell level logic when a cell is unselected
657 * @method unselect
625 * @method unselect
658 * @return is the action being taken
626 * @return is the action being taken
659 */
627 */
660 CodeCell.prototype.unselect = function () {
628 CodeCell.prototype.unselect = function () {
661 var cont = Cell.prototype.unselect.apply(this);
629 var cont = Cell.prototype.unselect.apply(this);
662 if (cont) {
630 if (cont) {
663 // When a code cell is usnelected, make sure that the corresponding
631 // When a code cell is usnelected, make sure that the corresponding
664 // tooltip and completer to that cell is closed.
632 // tooltip and completer to that cell is closed.
665 this.tooltip.remove_and_cancel_tooltip(true);
633 this.tooltip.remove_and_cancel_tooltip(true);
666 if (this.completer !== null) {
634 if (this.completer !== null) {
667 this.completer.close();
635 this.completer.close();
668 }
636 }
669 }
637 }
670 return cont;
638 return cont;
671 };
639 };
672
640
673 // Backwards compatability.
641 // Backwards compatability.
674 IPython.CodeCell = CodeCell;
642 IPython.CodeCell = CodeCell;
675
643
676 return {'CodeCell': CodeCell};
644 return {'CodeCell': CodeCell};
677 });
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,61 +1,64 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 /**
13 * Public constructor
14 */
12 initialize: function (parameters) {
15 initialize: function (parameters) {
13 /**
14 * Public constructor
15 */
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 /**
21 * Called when view is rendered.
22 */
20 render: function(){
23 render: function(){
21 /**
22 * Called when view is rendered.
23 */
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 _handle_route_msg: function(content) {
46 /**
47 var cell = this.options.cell;
47 * Handles re-routed iopub messages.
48 if (content && cell) {
48 */
49 if (content.method == 'push') {
49 _handle_route_msg: function(msg) {
50 cell.push_output_area(this.output_area);
50 if (msg) {
51 } else if (content.method == 'pop') {
51 var msg_type = msg.msg_type;
52 cell.pop_output_area(this.output_area);
52 if (msg_type=='clear_output') {
53 this.output_area.handle_clear_output(msg);
54 } else {
55 this.output_area.handle_output(msg);
53 }
56 }
54 }
57 }
55 },
58 },
56 });
59 });
57
60
58 return {
61 return {
59 'OutputView': OutputView,
62 'OutputView': OutputView,
60 };
63 };
61 });
64 });
@@ -1,50 +1,78 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 from IPython.kernel.zmq.session import Message
14
15
15 @skip_doctest
16 @skip_doctest
16 class Output(DOMWidget):
17 class Output(DOMWidget):
17 """Widget used as a context manager to display output.
18 """Widget used as a context manager to display output.
18
19
19 This widget can capture and display stdout, stderr, and rich output. To use
20 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
21 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
22 manager. Any output produced while in it's context will be captured and
22 displayed in it instead of the standard output area.
23 displayed in it instead of the standard output area.
23
24
24 Example
25 Example
25 from IPython.html import widgets
26 from IPython.html import widgets
26 from IPython.display import display
27 from IPython.display import display
27 out = widgets.Output()
28 out = widgets.Output()
28 display(out)
29 display(out)
29
30
30 print('prints to output area')
31 print('prints to output area')
31
32
32 with out:
33 with out:
33 print('prints to output widget')"""
34 print('prints to output widget')"""
34 _view_name = Unicode('OutputView', sync=True)
35 _view_name = Unicode('OutputView', sync=True)
35
36
36 def clear_output(self, *pargs, **kwargs):
37 def clear_output(self, *pargs, **kwargs):
37 with self:
38 with self:
38 clear_output(*pargs, **kwargs)
39 clear_output(*pargs, **kwargs)
39
40
40 def __enter__(self):
41 def __enter__(self):
42 """Called upon entering output widget context manager."""
41 self._flush()
43 self._flush()
42 self.send({'method': 'push'})
44 kernel = get_ipython().kernel
45 session = kernel.session
46 send = session.send
47 self._original_send = send
48 self._session = session
49
50 def send_hook(stream, msg_or_type, content=None, parent=None, ident=None,
51 buffers=None, track=False, header=None, metadata=None):
52
53 # Handle both prebuild messages and unbuilt messages.
54 if isinstance(msg_or_type, (Message, dict)):
55 msg_type = msg_or_type['msg_type']
56 msg = dict(msg_or_type)
57 else:
58 msg_type = msg_or_type
59 msg = session.msg(msg_type, content=content, parent=parent,
60 header=header, metadata=metadata)
61
62 # If this is a message type that we want to forward, forward it.
63 if stream is kernel.iopub_socket and msg_type in ['clear_output', 'stream', 'display_data']:
64 self.send(msg)
65 else:
66 send(stream, msg, ident=ident, buffers=buffers, track=track)
67
68 session.send = send_hook
43
69
44 def __exit__(self, exception_type, exception_value, traceback):
70 def __exit__(self, exception_type, exception_value, traceback):
71 """Called upon exiting output widget context manager."""
45 self._flush()
72 self._flush()
46 self.send({'method': 'pop'})
73 self._session.send = self._original_send
47
74
48 def _flush(self):
75 def _flush(self):
76 """Flush stdout and stderr buffers."""
49 sys.stdout.flush()
77 sys.stdout.flush()
50 sys.stderr.flush()
78 sys.stderr.flush()
General Comments 0
You need to be logged in to leave comments. Login now