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