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