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