##// END OF EJS Templates
Merge pull request #7733 from minrk/esc-tooltip...
Jonathan Frederic -
r20400:a9d795a2 merge
parent child Browse files
Show More
@@ -1,663 +1,664 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._ipkmIgnore = true;
332 event.preventDefault();
333 event.preventDefault();
333 return true;
334 return true;
334 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
335 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
335 if (editor.somethingSelected() || editor.getSelections().length !== 1){
336 if (editor.somethingSelected() || editor.getSelections().length !== 1){
336 var anchor = editor.getCursor("anchor");
337 var anchor = editor.getCursor("anchor");
337 var head = editor.getCursor("head");
338 var head = editor.getCursor("head");
338 if( anchor.line !== head.line){
339 if( anchor.line !== head.line){
339 return false;
340 return false;
340 }
341 }
341 }
342 }
342 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
343 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
343 if (pre_cursor.trim() === "") {
344 if (pre_cursor.trim() === "") {
344 // Don't show tooltip if the part of the line before the cursor
345 // Don't show tooltip if the part of the line before the cursor
345 // is empty. In this case, let CodeMirror handle indentation.
346 // is empty. In this case, let CodeMirror handle indentation.
346 return false;
347 return false;
347 }
348 }
348 this.tooltip.request(that);
349 this.tooltip.request(that);
349 event.codemirrorIgnore = true;
350 event.codemirrorIgnore = true;
350 event.preventDefault();
351 event.preventDefault();
351 return true;
352 return true;
352 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
353 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
353 // Tab completion.
354 // Tab completion.
354 this.tooltip.remove_and_cancel_tooltip();
355 this.tooltip.remove_and_cancel_tooltip();
355
356
356 // completion does not work on multicursor, it might be possible though in some cases
357 // completion does not work on multicursor, it might be possible though in some cases
357 if (editor.somethingSelected() || editor.getSelections().length > 1) {
358 if (editor.somethingSelected() || editor.getSelections().length > 1) {
358 return false;
359 return false;
359 }
360 }
360 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
361 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
361 if (pre_cursor.trim() === "") {
362 if (pre_cursor.trim() === "") {
362 // Don't autocomplete if the part of the line before the cursor
363 // Don't autocomplete if the part of the line before the cursor
363 // is empty. In this case, let CodeMirror handle indentation.
364 // is empty. In this case, let CodeMirror handle indentation.
364 return false;
365 return false;
365 } else {
366 } else {
366 event.codemirrorIgnore = true;
367 event.codemirrorIgnore = true;
367 event.preventDefault();
368 event.preventDefault();
368 this.completer.startCompletion();
369 this.completer.startCompletion();
369 return true;
370 return true;
370 }
371 }
371 }
372 }
372
373
373 // keyboard event wasn't one of those unique to code cells, let's see
374 // keyboard event wasn't one of those unique to code cells, let's see
374 // if it's one of the generic ones (i.e. check edit mode shortcuts)
375 // if it's one of the generic ones (i.e. check edit mode shortcuts)
375 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
376 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
376 };
377 };
377
378
378 // Kernel related calls.
379 // Kernel related calls.
379
380
380 CodeCell.prototype.set_kernel = function (kernel) {
381 CodeCell.prototype.set_kernel = function (kernel) {
381 this.kernel = kernel;
382 this.kernel = kernel;
382 };
383 };
383
384
384 /**
385 /**
385 * Execute current code cell to the kernel
386 * Execute current code cell to the kernel
386 * @method execute
387 * @method execute
387 */
388 */
388 CodeCell.prototype.execute = function (stop_on_error) {
389 CodeCell.prototype.execute = function (stop_on_error) {
389 if (!this.kernel || !this.kernel.is_connected()) {
390 if (!this.kernel || !this.kernel.is_connected()) {
390 console.log("Can't execute, kernel is not connected.");
391 console.log("Can't execute, kernel is not connected.");
391 return;
392 return;
392 }
393 }
393
394
394 this.output_area.clear_output(false, true);
395 this.output_area.clear_output(false, true);
395
396
396 if (stop_on_error === undefined) {
397 if (stop_on_error === undefined) {
397 stop_on_error = true;
398 stop_on_error = true;
398 }
399 }
399
400
400 // Clear widget area
401 // Clear widget area
401 for (var i = 0; i < this.widget_views.length; i++) {
402 for (var i = 0; i < this.widget_views.length; i++) {
402 var view = this.widget_views[i];
403 var view = this.widget_views[i];
403 view.remove();
404 view.remove();
404
405
405 // Remove widget live events.
406 // Remove widget live events.
406 view.off('comm:live', this._widget_live);
407 view.off('comm:live', this._widget_live);
407 view.off('comm:dead', this._widget_dead);
408 view.off('comm:dead', this._widget_dead);
408 }
409 }
409 this.widget_views = [];
410 this.widget_views = [];
410 this.widget_subarea.html('');
411 this.widget_subarea.html('');
411 this.widget_subarea.height('');
412 this.widget_subarea.height('');
412 this.widget_area.height('');
413 this.widget_area.height('');
413 this.widget_area.hide();
414 this.widget_area.hide();
414
415
415 var old_msg_id = this.last_msg_id;
416 var old_msg_id = this.last_msg_id;
416
417
417 if (old_msg_id) {
418 if (old_msg_id) {
418 this.kernel.clear_callbacks_for_msg(old_msg_id);
419 this.kernel.clear_callbacks_for_msg(old_msg_id);
419 if (old_msg_id) {
420 if (old_msg_id) {
420 delete CodeCell.msg_cells[old_msg_id];
421 delete CodeCell.msg_cells[old_msg_id];
421 }
422 }
422 }
423 }
423 if (this.get_text().trim().length === 0) {
424 if (this.get_text().trim().length === 0) {
424 // nothing to do
425 // nothing to do
425 this.set_input_prompt(null);
426 this.set_input_prompt(null);
426 return;
427 return;
427 }
428 }
428 this.set_input_prompt('*');
429 this.set_input_prompt('*');
429 this.element.addClass("running");
430 this.element.addClass("running");
430 var callbacks = this.get_callbacks();
431 var callbacks = this.get_callbacks();
431
432
432 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
433 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
433 stop_on_error : stop_on_error});
434 stop_on_error : stop_on_error});
434 CodeCell.msg_cells[this.last_msg_id] = this;
435 CodeCell.msg_cells[this.last_msg_id] = this;
435 this.render();
436 this.render();
436 this.events.trigger('execute.CodeCell', {cell: this});
437 this.events.trigger('execute.CodeCell', {cell: this});
437 };
438 };
438
439
439 /**
440 /**
440 * Construct the default callbacks for
441 * Construct the default callbacks for
441 * @method get_callbacks
442 * @method get_callbacks
442 */
443 */
443 CodeCell.prototype.get_callbacks = function () {
444 CodeCell.prototype.get_callbacks = function () {
444 var that = this;
445 var that = this;
445 return {
446 return {
446 shell : {
447 shell : {
447 reply : $.proxy(this._handle_execute_reply, this),
448 reply : $.proxy(this._handle_execute_reply, this),
448 payload : {
449 payload : {
449 set_next_input : $.proxy(this._handle_set_next_input, this),
450 set_next_input : $.proxy(this._handle_set_next_input, this),
450 page : $.proxy(this._open_with_pager, this)
451 page : $.proxy(this._open_with_pager, this)
451 }
452 }
452 },
453 },
453 iopub : {
454 iopub : {
454 output : function() {
455 output : function() {
455 that.output_area.handle_output.apply(that.output_area, arguments);
456 that.output_area.handle_output.apply(that.output_area, arguments);
456 },
457 },
457 clear_output : function() {
458 clear_output : function() {
458 that.output_area.handle_clear_output.apply(that.output_area, arguments);
459 that.output_area.handle_clear_output.apply(that.output_area, arguments);
459 },
460 },
460 },
461 },
461 input : $.proxy(this._handle_input_request, this)
462 input : $.proxy(this._handle_input_request, this)
462 };
463 };
463 };
464 };
464
465
465 CodeCell.prototype._open_with_pager = function (payload) {
466 CodeCell.prototype._open_with_pager = function (payload) {
466 this.events.trigger('open_with_text.Pager', payload);
467 this.events.trigger('open_with_text.Pager', payload);
467 };
468 };
468
469
469 /**
470 /**
470 * @method _handle_execute_reply
471 * @method _handle_execute_reply
471 * @private
472 * @private
472 */
473 */
473 CodeCell.prototype._handle_execute_reply = function (msg) {
474 CodeCell.prototype._handle_execute_reply = function (msg) {
474 this.set_input_prompt(msg.content.execution_count);
475 this.set_input_prompt(msg.content.execution_count);
475 this.element.removeClass("running");
476 this.element.removeClass("running");
476 this.events.trigger('set_dirty.Notebook', {value: true});
477 this.events.trigger('set_dirty.Notebook', {value: true});
477 };
478 };
478
479
479 /**
480 /**
480 * @method _handle_set_next_input
481 * @method _handle_set_next_input
481 * @private
482 * @private
482 */
483 */
483 CodeCell.prototype._handle_set_next_input = function (payload) {
484 CodeCell.prototype._handle_set_next_input = function (payload) {
484 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
485 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
485 this.events.trigger('set_next_input.Notebook', data);
486 this.events.trigger('set_next_input.Notebook', data);
486 };
487 };
487
488
488 /**
489 /**
489 * @method _handle_input_request
490 * @method _handle_input_request
490 * @private
491 * @private
491 */
492 */
492 CodeCell.prototype._handle_input_request = function (msg) {
493 CodeCell.prototype._handle_input_request = function (msg) {
493 this.output_area.append_raw_input(msg);
494 this.output_area.append_raw_input(msg);
494 };
495 };
495
496
496
497
497 // Basic cell manipulation.
498 // Basic cell manipulation.
498
499
499 CodeCell.prototype.select = function () {
500 CodeCell.prototype.select = function () {
500 var cont = Cell.prototype.select.apply(this);
501 var cont = Cell.prototype.select.apply(this);
501 if (cont) {
502 if (cont) {
502 this.code_mirror.refresh();
503 this.code_mirror.refresh();
503 this.auto_highlight();
504 this.auto_highlight();
504 }
505 }
505 return cont;
506 return cont;
506 };
507 };
507
508
508 CodeCell.prototype.render = function () {
509 CodeCell.prototype.render = function () {
509 var cont = Cell.prototype.render.apply(this);
510 var cont = Cell.prototype.render.apply(this);
510 // Always execute, even if we are already in the rendered state
511 // Always execute, even if we are already in the rendered state
511 return cont;
512 return cont;
512 };
513 };
513
514
514 CodeCell.prototype.select_all = function () {
515 CodeCell.prototype.select_all = function () {
515 var start = {line: 0, ch: 0};
516 var start = {line: 0, ch: 0};
516 var nlines = this.code_mirror.lineCount();
517 var nlines = this.code_mirror.lineCount();
517 var last_line = this.code_mirror.getLine(nlines-1);
518 var last_line = this.code_mirror.getLine(nlines-1);
518 var end = {line: nlines-1, ch: last_line.length};
519 var end = {line: nlines-1, ch: last_line.length};
519 this.code_mirror.setSelection(start, end);
520 this.code_mirror.setSelection(start, end);
520 };
521 };
521
522
522
523
523 CodeCell.prototype.collapse_output = function () {
524 CodeCell.prototype.collapse_output = function () {
524 this.output_area.collapse();
525 this.output_area.collapse();
525 };
526 };
526
527
527
528
528 CodeCell.prototype.expand_output = function () {
529 CodeCell.prototype.expand_output = function () {
529 this.output_area.expand();
530 this.output_area.expand();
530 this.output_area.unscroll_area();
531 this.output_area.unscroll_area();
531 };
532 };
532
533
533 CodeCell.prototype.scroll_output = function () {
534 CodeCell.prototype.scroll_output = function () {
534 this.output_area.expand();
535 this.output_area.expand();
535 this.output_area.scroll_if_long();
536 this.output_area.scroll_if_long();
536 };
537 };
537
538
538 CodeCell.prototype.toggle_output = function () {
539 CodeCell.prototype.toggle_output = function () {
539 this.output_area.toggle_output();
540 this.output_area.toggle_output();
540 };
541 };
541
542
542 CodeCell.prototype.toggle_output_scroll = function () {
543 CodeCell.prototype.toggle_output_scroll = function () {
543 this.output_area.toggle_scroll();
544 this.output_area.toggle_scroll();
544 };
545 };
545
546
546
547
547 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
548 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
548 var ns;
549 var ns;
549 if (prompt_value === undefined || prompt_value === null) {
550 if (prompt_value === undefined || prompt_value === null) {
550 ns = "&nbsp;";
551 ns = "&nbsp;";
551 } else {
552 } else {
552 ns = encodeURIComponent(prompt_value);
553 ns = encodeURIComponent(prompt_value);
553 }
554 }
554 return 'In&nbsp;[' + ns + ']:';
555 return 'In&nbsp;[' + ns + ']:';
555 };
556 };
556
557
557 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
558 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
558 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
559 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
559 for(var i=1; i < lines_number; i++) {
560 for(var i=1; i < lines_number; i++) {
560 html.push(['...:']);
561 html.push(['...:']);
561 }
562 }
562 return html.join('<br/>');
563 return html.join('<br/>');
563 };
564 };
564
565
565 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
566 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
566
567
567
568
568 CodeCell.prototype.set_input_prompt = function (number) {
569 CodeCell.prototype.set_input_prompt = function (number) {
569 var nline = 1;
570 var nline = 1;
570 if (this.code_mirror !== undefined) {
571 if (this.code_mirror !== undefined) {
571 nline = this.code_mirror.lineCount();
572 nline = this.code_mirror.lineCount();
572 }
573 }
573 this.input_prompt_number = number;
574 this.input_prompt_number = number;
574 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
575 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
575 // This HTML call is okay because the user contents are escaped.
576 // This HTML call is okay because the user contents are escaped.
576 this.element.find('div.input_prompt').html(prompt_html);
577 this.element.find('div.input_prompt').html(prompt_html);
577 };
578 };
578
579
579
580
580 CodeCell.prototype.clear_input = function () {
581 CodeCell.prototype.clear_input = function () {
581 this.code_mirror.setValue('');
582 this.code_mirror.setValue('');
582 };
583 };
583
584
584
585
585 CodeCell.prototype.get_text = function () {
586 CodeCell.prototype.get_text = function () {
586 return this.code_mirror.getValue();
587 return this.code_mirror.getValue();
587 };
588 };
588
589
589
590
590 CodeCell.prototype.set_text = function (code) {
591 CodeCell.prototype.set_text = function (code) {
591 return this.code_mirror.setValue(code);
592 return this.code_mirror.setValue(code);
592 };
593 };
593
594
594
595
595 CodeCell.prototype.clear_output = function (wait) {
596 CodeCell.prototype.clear_output = function (wait) {
596 this.output_area.clear_output(wait);
597 this.output_area.clear_output(wait);
597 this.set_input_prompt();
598 this.set_input_prompt();
598 };
599 };
599
600
600
601
601 // JSON serialization
602 // JSON serialization
602
603
603 CodeCell.prototype.fromJSON = function (data) {
604 CodeCell.prototype.fromJSON = function (data) {
604 Cell.prototype.fromJSON.apply(this, arguments);
605 Cell.prototype.fromJSON.apply(this, arguments);
605 if (data.cell_type === 'code') {
606 if (data.cell_type === 'code') {
606 if (data.source !== undefined) {
607 if (data.source !== undefined) {
607 this.set_text(data.source);
608 this.set_text(data.source);
608 // make this value the starting point, so that we can only undo
609 // make this value the starting point, so that we can only undo
609 // to this state, instead of a blank cell
610 // to this state, instead of a blank cell
610 this.code_mirror.clearHistory();
611 this.code_mirror.clearHistory();
611 this.auto_highlight();
612 this.auto_highlight();
612 }
613 }
613 this.set_input_prompt(data.execution_count);
614 this.set_input_prompt(data.execution_count);
614 this.output_area.trusted = data.metadata.trusted || false;
615 this.output_area.trusted = data.metadata.trusted || false;
615 this.output_area.fromJSON(data.outputs, data.metadata);
616 this.output_area.fromJSON(data.outputs, data.metadata);
616 }
617 }
617 };
618 };
618
619
619
620
620 CodeCell.prototype.toJSON = function () {
621 CodeCell.prototype.toJSON = function () {
621 var data = Cell.prototype.toJSON.apply(this);
622 var data = Cell.prototype.toJSON.apply(this);
622 data.source = this.get_text();
623 data.source = this.get_text();
623 // is finite protect against undefined and '*' value
624 // is finite protect against undefined and '*' value
624 if (isFinite(this.input_prompt_number)) {
625 if (isFinite(this.input_prompt_number)) {
625 data.execution_count = this.input_prompt_number;
626 data.execution_count = this.input_prompt_number;
626 } else {
627 } else {
627 data.execution_count = null;
628 data.execution_count = null;
628 }
629 }
629 var outputs = this.output_area.toJSON();
630 var outputs = this.output_area.toJSON();
630 data.outputs = outputs;
631 data.outputs = outputs;
631 data.metadata.trusted = this.output_area.trusted;
632 data.metadata.trusted = this.output_area.trusted;
632 data.metadata.collapsed = this.output_area.collapsed;
633 data.metadata.collapsed = this.output_area.collapsed;
633 if (this.output_area.scroll_state === 'auto') {
634 if (this.output_area.scroll_state === 'auto') {
634 delete data.metadata.scrolled;
635 delete data.metadata.scrolled;
635 } else {
636 } else {
636 data.metadata.scrolled = this.output_area.scroll_state;
637 data.metadata.scrolled = this.output_area.scroll_state;
637 }
638 }
638 return data;
639 return data;
639 };
640 };
640
641
641 /**
642 /**
642 * handle cell level logic when a cell is unselected
643 * handle cell level logic when a cell is unselected
643 * @method unselect
644 * @method unselect
644 * @return is the action being taken
645 * @return is the action being taken
645 */
646 */
646 CodeCell.prototype.unselect = function () {
647 CodeCell.prototype.unselect = function () {
647 var cont = Cell.prototype.unselect.apply(this);
648 var cont = Cell.prototype.unselect.apply(this);
648 if (cont) {
649 if (cont) {
649 // When a code cell is usnelected, make sure that the corresponding
650 // When a code cell is usnelected, make sure that the corresponding
650 // tooltip and completer to that cell is closed.
651 // tooltip and completer to that cell is closed.
651 this.tooltip.remove_and_cancel_tooltip(true);
652 this.tooltip.remove_and_cancel_tooltip(true);
652 if (this.completer !== null) {
653 if (this.completer !== null) {
653 this.completer.close();
654 this.completer.close();
654 }
655 }
655 }
656 }
656 return cont;
657 return cont;
657 };
658 };
658
659
659 // Backwards compatability.
660 // Backwards compatability.
660 IPython.CodeCell = CodeCell;
661 IPython.CodeCell = CodeCell;
661
662
662 return {'CodeCell': CodeCell};
663 return {'CodeCell': CodeCell};
663 });
664 });
General Comments 0
You need to be logged in to leave comments. Login now