##// END OF EJS Templates
Final touches?
Jonathan Frederic -
Show More
@@ -1,495 +1,494
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Cell
9 // Cell
10 //============================================================================
10 //============================================================================
11 /**
11 /**
12 * An extendable module that provide base functionnality to create cell for notebook.
12 * An extendable module that provide base functionnality to create cell for notebook.
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule Cell
15 * @submodule Cell
16 */
16 */
17
17
18 var IPython = (function (IPython) {
18 var IPython = (function (IPython) {
19 "use strict";
19 "use strict";
20
20
21 var utils = IPython.utils;
21 var utils = IPython.utils;
22
22
23 /**
23 /**
24 * The Base `Cell` class from which to inherit
24 * The Base `Cell` class from which to inherit
25 * @class Cell
25 * @class Cell
26 **/
26 **/
27
27
28 /*
28 /*
29 * @constructor
29 * @constructor
30 *
30 *
31 * @param {object|undefined} [options]
31 * @param {object|undefined} [options]
32 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
32 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend default parameters
33 */
33 */
34 var Cell = function (options) {
34 var Cell = function (options) {
35
35
36 options = this.mergeopt(Cell, options);
36 options = this.mergeopt(Cell, options);
37 // superclass default overwrite our default
37 // superclass default overwrite our default
38
38
39 this.placeholder = options.placeholder || '';
39 this.placeholder = options.placeholder || '';
40 this.read_only = options.cm_config.readOnly;
40 this.read_only = options.cm_config.readOnly;
41 this.selected = false;
41 this.selected = false;
42 this.rendered = false;
42 this.rendered = false;
43 this.mode = 'command';
43 this.mode = 'command';
44 this.metadata = {};
44 this.metadata = {};
45 // load this from metadata later ?
45 // load this from metadata later ?
46 this.user_highlight = 'auto';
46 this.user_highlight = 'auto';
47 this.cm_config = options.cm_config;
47 this.cm_config = options.cm_config;
48 this.cell_id = utils.uuid();
48 this.cell_id = utils.uuid();
49 this._options = options;
49 this._options = options;
50
50
51 // For JS VM engines optimization, attributes should be all set (even
51 // For JS VM engines optimization, attributes should be all set (even
52 // to null) in the constructor, and if possible, if different subclass
52 // to null) in the constructor, and if possible, if different subclass
53 // have new attributes with same name, they should be created in the
53 // have new attributes with same name, they should be created in the
54 // same order. Easiest is to create and set to null in parent class.
54 // same order. Easiest is to create and set to null in parent class.
55
55
56 this.element = null;
56 this.element = null;
57 this.cell_type = this.cell_type || null;
57 this.cell_type = this.cell_type || null;
58 this.code_mirror = null;
58 this.code_mirror = null;
59
59
60 this.create_element();
60 this.create_element();
61 if (this.element !== null) {
61 if (this.element !== null) {
62 this.element.data("cell", this);
62 this.element.data("cell", this);
63 this.bind_events();
63 this.bind_events();
64 this.init_classes();
64 this.init_classes();
65 }
65 }
66 };
66 };
67
67
68 Cell.options_default = {
68 Cell.options_default = {
69 cm_config : {
69 cm_config : {
70 indentUnit : 4,
70 indentUnit : 4,
71 readOnly: false,
71 readOnly: false,
72 theme: "default"
72 theme: "default"
73 }
73 }
74 };
74 };
75
75
76 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
76 // FIXME: Workaround CM Bug #332 (Safari segfault on drag)
77 // by disabling drag/drop altogether on Safari
77 // by disabling drag/drop altogether on Safari
78 // https://github.com/marijnh/CodeMirror/issues/332
78 // https://github.com/marijnh/CodeMirror/issues/332
79 if (utils.browser[0] == "Safari") {
79 if (utils.browser[0] == "Safari") {
80 Cell.options_default.cm_config.dragDrop = false;
80 Cell.options_default.cm_config.dragDrop = false;
81 }
81 }
82
82
83 Cell.prototype.mergeopt = function(_class, options, overwrite){
83 Cell.prototype.mergeopt = function(_class, options, overwrite){
84 options = options || {};
84 options = options || {};
85 overwrite = overwrite || {};
85 overwrite = overwrite || {};
86 return $.extend(true, {}, _class.options_default, options, overwrite);
86 return $.extend(true, {}, _class.options_default, options, overwrite);
87 };
87 };
88
88
89 /**
89 /**
90 * Empty. Subclasses must implement create_element.
90 * Empty. Subclasses must implement create_element.
91 * This should contain all the code to create the DOM element in notebook
91 * This should contain all the code to create the DOM element in notebook
92 * and will be called by Base Class constructor.
92 * and will be called by Base Class constructor.
93 * @method create_element
93 * @method create_element
94 */
94 */
95 Cell.prototype.create_element = function () {
95 Cell.prototype.create_element = function () {
96 };
96 };
97
97
98 Cell.prototype.init_classes = function () {
98 Cell.prototype.init_classes = function () {
99 // Call after this.element exists to initialize the css classes
99 // Call after this.element exists to initialize the css classes
100 // related to selected, rendered and mode.
100 // related to selected, rendered and mode.
101 if (this.selected) {
101 if (this.selected) {
102 this.element.addClass('selected');
102 this.element.addClass('selected');
103 } else {
103 } else {
104 this.element.addClass('unselected');
104 this.element.addClass('unselected');
105 }
105 }
106 if (this.rendered) {
106 if (this.rendered) {
107 this.element.addClass('rendered');
107 this.element.addClass('rendered');
108 } else {
108 } else {
109 this.element.addClass('unrendered');
109 this.element.addClass('unrendered');
110 }
110 }
111 if (this.mode === 'edit') {
111 if (this.mode === 'edit') {
112 this.element.addClass('edit_mode');
112 this.element.addClass('edit_mode');
113 } else {
113 } else {
114 this.element.addClass('command_mode');
114 this.element.addClass('command_mode');
115 }
115 }
116 };
116 };
117
117
118
119 /**
118 /**
120 * Subclasses can implement override bind_events.
119 * Subclasses can implement override bind_events.
121 * Be carefull to call the parent method when overwriting as it fires event.
120 * Be carefull to call the parent method when overwriting as it fires event.
122 * this will be triggerd after create_element in constructor.
121 * this will be triggerd after create_element in constructor.
123 * @method bind_events
122 * @method bind_events
124 */
123 */
125 Cell.prototype.bind_events = function () {
124 Cell.prototype.bind_events = function () {
126 var that = this;
125 var that = this;
127 // We trigger events so that Cell doesn't have to depend on Notebook.
126 // We trigger events so that Cell doesn't have to depend on Notebook.
128 that.element.click(function (event) {
127 that.element.click(function (event) {
129 if (!that.selected) {
128 if (!that.selected) {
130 $([IPython.events]).trigger('select.Cell', {'cell':that});
129 $([IPython.events]).trigger('select.Cell', {'cell':that});
131 }
130 }
132 });
131 });
133 that.element.focusin(function (event) {
132 that.element.focusin(function (event) {
134 if (!that.selected) {
133 if (!that.selected) {
135 $([IPython.events]).trigger('select.Cell', {'cell':that});
134 $([IPython.events]).trigger('select.Cell', {'cell':that});
136 }
135 }
137 });
136 });
138 if (this.code_mirror) {
137 if (this.code_mirror) {
139 this.code_mirror.on("change", function(cm, change) {
138 this.code_mirror.on("change", function(cm, change) {
140 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
139 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
141 });
140 });
142 }
141 }
143 if (this.code_mirror) {
142 if (this.code_mirror) {
144 this.code_mirror.on('focus', function(cm, change) {
143 this.code_mirror.on('focus', function(cm, change) {
145 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
144 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
146 });
145 });
147 }
146 }
148 if (this.code_mirror) {
147 if (this.code_mirror) {
149 this.code_mirror.on('blur', function(cm, change) {
148 this.code_mirror.on('blur', function(cm, change) {
150 // Check if this unfocus event is legit.
149 // Check if this unfocus event is legit.
151 if (!that.should_cancel_blur()) {
150 if (!that.should_cancel_blur()) {
152 $([IPython.events]).trigger('command_mode.Cell', {cell: that});
151 $([IPython.events]).trigger('command_mode.Cell', {cell: that});
153 }
152 }
154 });
153 });
155 }
154 }
156 };
155 };
157
156
158 /**
157 /**
159 * Triger typsetting of math by mathjax on current cell element
158 * Triger typsetting of math by mathjax on current cell element
160 * @method typeset
159 * @method typeset
161 */
160 */
162 Cell.prototype.typeset = function () {
161 Cell.prototype.typeset = function () {
163 if (window.MathJax) {
162 if (window.MathJax) {
164 var cell_math = this.element.get(0);
163 var cell_math = this.element.get(0);
165 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
164 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
166 }
165 }
167 };
166 };
168
167
169 /**
168 /**
170 * handle cell level logic when a cell is selected
169 * handle cell level logic when a cell is selected
171 * @method select
170 * @method select
172 * @return is the action being taken
171 * @return is the action being taken
173 */
172 */
174 Cell.prototype.select = function () {
173 Cell.prototype.select = function () {
175 if (!this.selected) {
174 if (!this.selected) {
176 this.element.addClass('selected');
175 this.element.addClass('selected');
177 this.element.removeClass('unselected');
176 this.element.removeClass('unselected');
178 this.selected = true;
177 this.selected = true;
179 return true;
178 return true;
180 } else {
179 } else {
181 return false;
180 return false;
182 }
181 }
183 };
182 };
184
183
185 /**
184 /**
186 * handle cell level logic when a cell is unselected
185 * handle cell level logic when a cell is unselected
187 * @method unselect
186 * @method unselect
188 * @return is the action being taken
187 * @return is the action being taken
189 */
188 */
190 Cell.prototype.unselect = function () {
189 Cell.prototype.unselect = function () {
191 if (this.selected) {
190 if (this.selected) {
192 this.element.addClass('unselected');
191 this.element.addClass('unselected');
193 this.element.removeClass('selected');
192 this.element.removeClass('selected');
194 this.selected = false;
193 this.selected = false;
195 return true;
194 return true;
196 } else {
195 } else {
197 return false;
196 return false;
198 }
197 }
199 };
198 };
200
199
201 /**
200 /**
202 * handle cell level logic when a cell is rendered
201 * handle cell level logic when a cell is rendered
203 * @method render
202 * @method render
204 * @return is the action being taken
203 * @return is the action being taken
205 */
204 */
206 Cell.prototype.render = function () {
205 Cell.prototype.render = function () {
207 if (!this.rendered) {
206 if (!this.rendered) {
208 this.element.addClass('rendered');
207 this.element.addClass('rendered');
209 this.element.removeClass('unrendered');
208 this.element.removeClass('unrendered');
210 this.rendered = true;
209 this.rendered = true;
211 return true;
210 return true;
212 } else {
211 } else {
213 return false;
212 return false;
214 }
213 }
215 };
214 };
216
215
217 /**
216 /**
218 * handle cell level logic when a cell is unrendered
217 * handle cell level logic when a cell is unrendered
219 * @method unrender
218 * @method unrender
220 * @return is the action being taken
219 * @return is the action being taken
221 */
220 */
222 Cell.prototype.unrender = function () {
221 Cell.prototype.unrender = function () {
223 if (this.rendered) {
222 if (this.rendered) {
224 this.element.addClass('unrendered');
223 this.element.addClass('unrendered');
225 this.element.removeClass('rendered');
224 this.element.removeClass('rendered');
226 this.rendered = false;
225 this.rendered = false;
227 return true;
226 return true;
228 } else {
227 } else {
229 return false;
228 return false;
230 }
229 }
231 };
230 };
232
231
233 /**
232 /**
234 * enter the command mode for the cell
233 * enter the command mode for the cell
235 * @method command_mode
234 * @method command_mode
236 * @return is the action being taken
235 * @return is the action being taken
237 */
236 */
238 Cell.prototype.command_mode = function () {
237 Cell.prototype.command_mode = function () {
239 if (this.mode !== 'command') {
238 if (this.mode !== 'command') {
240 this.element.addClass('command_mode');
239 this.element.addClass('command_mode');
241 this.element.removeClass('edit_mode');
240 this.element.removeClass('edit_mode');
242 this.mode = 'command';
241 this.mode = 'command';
243 return true;
242 return true;
244 } else {
243 } else {
245 return false;
244 return false;
246 }
245 }
247 };
246 };
248
247
249 /**
248 /**
250 * enter the edit mode for the cell
249 * enter the edit mode for the cell
251 * @method command_mode
250 * @method command_mode
252 * @return is the action being taken
251 * @return is the action being taken
253 */
252 */
254 Cell.prototype.edit_mode = function () {
253 Cell.prototype.edit_mode = function () {
255 if (this.mode !== 'edit') {
254 if (this.mode !== 'edit') {
256 this.element.addClass('edit_mode');
255 this.element.addClass('edit_mode');
257 this.element.removeClass('command_mode');
256 this.element.removeClass('command_mode');
258 this.mode = 'edit';
257 this.mode = 'edit';
259 return true;
258 return true;
260 } else {
259 } else {
261 return false;
260 return false;
262 }
261 }
263 };
262 };
264
263
265 /**
264 /**
266 * Determine whether or not the unfocus event should be aknowledged.
265 * Determine whether or not the unfocus event should be aknowledged.
267 *
266 *
268 * @method should_cancel_blur
267 * @method should_cancel_blur
269 *
268 *
270 * @return results {bool} Whether or not to ignore the cell's blur event.
269 * @return results {bool} Whether or not to ignore the cell's blur event.
271 **/
270 **/
272 Cell.prototype.should_cancel_blur = function () {
271 Cell.prototype.should_cancel_blur = function () {
273 return IPython.tooltip && IPython.tooltip.is_visible();
272 return IPython.tooltip && IPython.tooltip.is_visible();
274 };
273 };
275
274
276 /**
275 /**
277 * Focus the cell in the DOM sense
276 * Focus the cell in the DOM sense
278 * @method focus_cell
277 * @method focus_cell
279 */
278 */
280 Cell.prototype.focus_cell = function () {
279 Cell.prototype.focus_cell = function () {
281 this.element.focus();
280 this.element.focus();
282 };
281 };
283
282
284 /**
283 /**
285 * Focus the editor area so a user can type
284 * Focus the editor area so a user can type
286 *
285 *
287 * NOTE: If codemirror is focused via a mouse click event, you don't want to
286 * NOTE: If codemirror is focused via a mouse click event, you don't want to
288 * call this because it will cause a page jump.
287 * call this because it will cause a page jump.
289 * @method focus_editor
288 * @method focus_editor
290 */
289 */
291 Cell.prototype.focus_editor = function () {
290 Cell.prototype.focus_editor = function () {
292 this.code_mirror.focus();
291 this.code_mirror.focus();
293 };
292 };
294
293
295 /**
294 /**
296 * Refresh codemirror instance
295 * Refresh codemirror instance
297 * @method refresh
296 * @method refresh
298 */
297 */
299 Cell.prototype.refresh = function () {
298 Cell.prototype.refresh = function () {
300 this.code_mirror.refresh();
299 this.code_mirror.refresh();
301 };
300 };
302
301
303 /**
302 /**
304 * should be overritten by subclass
303 * should be overritten by subclass
305 * @method get_text
304 * @method get_text
306 */
305 */
307 Cell.prototype.get_text = function () {
306 Cell.prototype.get_text = function () {
308 };
307 };
309
308
310 /**
309 /**
311 * should be overritten by subclass
310 * should be overritten by subclass
312 * @method set_text
311 * @method set_text
313 * @param {string} text
312 * @param {string} text
314 */
313 */
315 Cell.prototype.set_text = function (text) {
314 Cell.prototype.set_text = function (text) {
316 };
315 };
317
316
318 /**
317 /**
319 * should be overritten by subclass
318 * should be overritten by subclass
320 * serialise cell to json.
319 * serialise cell to json.
321 * @method toJSON
320 * @method toJSON
322 **/
321 **/
323 Cell.prototype.toJSON = function () {
322 Cell.prototype.toJSON = function () {
324 var data = {};
323 var data = {};
325 data.metadata = this.metadata;
324 data.metadata = this.metadata;
326 data.cell_type = this.cell_type;
325 data.cell_type = this.cell_type;
327 return data;
326 return data;
328 };
327 };
329
328
330
329
331 /**
330 /**
332 * should be overritten by subclass
331 * should be overritten by subclass
333 * @method fromJSON
332 * @method fromJSON
334 **/
333 **/
335 Cell.prototype.fromJSON = function (data) {
334 Cell.prototype.fromJSON = function (data) {
336 if (data.metadata !== undefined) {
335 if (data.metadata !== undefined) {
337 this.metadata = data.metadata;
336 this.metadata = data.metadata;
338 }
337 }
339 this.celltoolbar.rebuild();
338 this.celltoolbar.rebuild();
340 };
339 };
341
340
342
341
343 /**
342 /**
344 * can the cell be split into two cells
343 * can the cell be split into two cells
345 * @method is_splittable
344 * @method is_splittable
346 **/
345 **/
347 Cell.prototype.is_splittable = function () {
346 Cell.prototype.is_splittable = function () {
348 return true;
347 return true;
349 };
348 };
350
349
351
350
352 /**
351 /**
353 * can the cell be merged with other cells
352 * can the cell be merged with other cells
354 * @method is_mergeable
353 * @method is_mergeable
355 **/
354 **/
356 Cell.prototype.is_mergeable = function () {
355 Cell.prototype.is_mergeable = function () {
357 return true;
356 return true;
358 };
357 };
359
358
360
359
361 /**
360 /**
362 * @return {String} - the text before the cursor
361 * @return {String} - the text before the cursor
363 * @method get_pre_cursor
362 * @method get_pre_cursor
364 **/
363 **/
365 Cell.prototype.get_pre_cursor = function () {
364 Cell.prototype.get_pre_cursor = function () {
366 var cursor = this.code_mirror.getCursor();
365 var cursor = this.code_mirror.getCursor();
367 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
366 var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
368 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
367 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
369 return text;
368 return text;
370 };
369 };
371
370
372
371
373 /**
372 /**
374 * @return {String} - the text after the cursor
373 * @return {String} - the text after the cursor
375 * @method get_post_cursor
374 * @method get_post_cursor
376 **/
375 **/
377 Cell.prototype.get_post_cursor = function () {
376 Cell.prototype.get_post_cursor = function () {
378 var cursor = this.code_mirror.getCursor();
377 var cursor = this.code_mirror.getCursor();
379 var last_line_num = this.code_mirror.lineCount()-1;
378 var last_line_num = this.code_mirror.lineCount()-1;
380 var last_line_len = this.code_mirror.getLine(last_line_num).length;
379 var last_line_len = this.code_mirror.getLine(last_line_num).length;
381 var end = {line:last_line_num, ch:last_line_len};
380 var end = {line:last_line_num, ch:last_line_len};
382 var text = this.code_mirror.getRange(cursor, end);
381 var text = this.code_mirror.getRange(cursor, end);
383 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
382 text = text.replace(/^\n+/, '').replace(/\n+$/, '');
384 return text;
383 return text;
385 };
384 };
386
385
387 /**
386 /**
388 * Show/Hide CodeMirror LineNumber
387 * Show/Hide CodeMirror LineNumber
389 * @method show_line_numbers
388 * @method show_line_numbers
390 *
389 *
391 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
390 * @param value {Bool} show (true), or hide (false) the line number in CodeMirror
392 **/
391 **/
393 Cell.prototype.show_line_numbers = function (value) {
392 Cell.prototype.show_line_numbers = function (value) {
394 this.code_mirror.setOption('lineNumbers', value);
393 this.code_mirror.setOption('lineNumbers', value);
395 this.code_mirror.refresh();
394 this.code_mirror.refresh();
396 };
395 };
397
396
398 /**
397 /**
399 * Toggle CodeMirror LineNumber
398 * Toggle CodeMirror LineNumber
400 * @method toggle_line_numbers
399 * @method toggle_line_numbers
401 **/
400 **/
402 Cell.prototype.toggle_line_numbers = function () {
401 Cell.prototype.toggle_line_numbers = function () {
403 var val = this.code_mirror.getOption('lineNumbers');
402 var val = this.code_mirror.getOption('lineNumbers');
404 this.show_line_numbers(!val);
403 this.show_line_numbers(!val);
405 };
404 };
406
405
407 /**
406 /**
408 * Force codemirror highlight mode
407 * Force codemirror highlight mode
409 * @method force_highlight
408 * @method force_highlight
410 * @param {object} - CodeMirror mode
409 * @param {object} - CodeMirror mode
411 **/
410 **/
412 Cell.prototype.force_highlight = function(mode) {
411 Cell.prototype.force_highlight = function(mode) {
413 this.user_highlight = mode;
412 this.user_highlight = mode;
414 this.auto_highlight();
413 this.auto_highlight();
415 };
414 };
416
415
417 /**
416 /**
418 * Try to autodetect cell highlight mode, or use selected mode
417 * Try to autodetect cell highlight mode, or use selected mode
419 * @methods _auto_highlight
418 * @methods _auto_highlight
420 * @private
419 * @private
421 * @param {String|object|undefined} - CodeMirror mode | 'auto'
420 * @param {String|object|undefined} - CodeMirror mode | 'auto'
422 **/
421 **/
423 Cell.prototype._auto_highlight = function (modes) {
422 Cell.prototype._auto_highlight = function (modes) {
424 //Here we handle manually selected modes
423 //Here we handle manually selected modes
425 var mode;
424 var mode;
426 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
425 if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
427 {
426 {
428 mode = this.user_highlight;
427 mode = this.user_highlight;
429 CodeMirror.autoLoadMode(this.code_mirror, mode);
428 CodeMirror.autoLoadMode(this.code_mirror, mode);
430 this.code_mirror.setOption('mode', mode);
429 this.code_mirror.setOption('mode', mode);
431 return;
430 return;
432 }
431 }
433 var current_mode = this.code_mirror.getOption('mode', mode);
432 var current_mode = this.code_mirror.getOption('mode', mode);
434 var first_line = this.code_mirror.getLine(0);
433 var first_line = this.code_mirror.getLine(0);
435 // loop on every pairs
434 // loop on every pairs
436 for(mode in modes) {
435 for(mode in modes) {
437 var regs = modes[mode].reg;
436 var regs = modes[mode].reg;
438 // only one key every time but regexp can't be keys...
437 // only one key every time but regexp can't be keys...
439 for(var i=0; i<regs.length; i++) {
438 for(var i=0; i<regs.length; i++) {
440 // here we handle non magic_modes
439 // here we handle non magic_modes
441 if(first_line.match(regs[i]) !== null) {
440 if(first_line.match(regs[i]) !== null) {
442 if(current_mode == mode){
441 if(current_mode == mode){
443 return;
442 return;
444 }
443 }
445 if (mode.search('magic_') !== 0) {
444 if (mode.search('magic_') !== 0) {
446 this.code_mirror.setOption('mode', mode);
445 this.code_mirror.setOption('mode', mode);
447 CodeMirror.autoLoadMode(this.code_mirror, mode);
446 CodeMirror.autoLoadMode(this.code_mirror, mode);
448 return;
447 return;
449 }
448 }
450 var open = modes[mode].open || "%%";
449 var open = modes[mode].open || "%%";
451 var close = modes[mode].close || "%%end";
450 var close = modes[mode].close || "%%end";
452 var mmode = mode;
451 var mmode = mode;
453 mode = mmode.substr(6);
452 mode = mmode.substr(6);
454 if(current_mode == mode){
453 if(current_mode == mode){
455 return;
454 return;
456 }
455 }
457 CodeMirror.autoLoadMode(this.code_mirror, mode);
456 CodeMirror.autoLoadMode(this.code_mirror, mode);
458 // create on the fly a mode that swhitch between
457 // create on the fly a mode that swhitch between
459 // plain/text and smth else otherwise `%%` is
458 // plain/text and smth else otherwise `%%` is
460 // source of some highlight issues.
459 // source of some highlight issues.
461 // we use patchedGetMode to circumvent a bug in CM
460 // we use patchedGetMode to circumvent a bug in CM
462 CodeMirror.defineMode(mmode , function(config) {
461 CodeMirror.defineMode(mmode , function(config) {
463 return CodeMirror.multiplexingMode(
462 return CodeMirror.multiplexingMode(
464 CodeMirror.patchedGetMode(config, 'text/plain'),
463 CodeMirror.patchedGetMode(config, 'text/plain'),
465 // always set someting on close
464 // always set someting on close
466 {open: open, close: close,
465 {open: open, close: close,
467 mode: CodeMirror.patchedGetMode(config, mode),
466 mode: CodeMirror.patchedGetMode(config, mode),
468 delimStyle: "delimit"
467 delimStyle: "delimit"
469 }
468 }
470 );
469 );
471 });
470 });
472 this.code_mirror.setOption('mode', mmode);
471 this.code_mirror.setOption('mode', mmode);
473 return;
472 return;
474 }
473 }
475 }
474 }
476 }
475 }
477 // fallback on default
476 // fallback on default
478 var default_mode;
477 var default_mode;
479 try {
478 try {
480 default_mode = this._options.cm_config.mode;
479 default_mode = this._options.cm_config.mode;
481 } catch(e) {
480 } catch(e) {
482 default_mode = 'text/plain';
481 default_mode = 'text/plain';
483 }
482 }
484 if( current_mode === default_mode){
483 if( current_mode === default_mode){
485 return;
484 return;
486 }
485 }
487 this.code_mirror.setOption('mode', default_mode);
486 this.code_mirror.setOption('mode', default_mode);
488 };
487 };
489
488
490 IPython.Cell = Cell;
489 IPython.Cell = Cell;
491
490
492 return IPython;
491 return IPython;
493
492
494 }(IPython));
493 }(IPython));
495
494
@@ -1,786 +1,786
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Keyboard management
9 // Keyboard management
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 // Setup global keycodes and inverse keycodes.
15 // Setup global keycodes and inverse keycodes.
16
16
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
17 // See http://unixpapa.com/js/key.html for a complete description. The short of
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
18 // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
20 // but have minor differences.
20 // but have minor differences.
21
21
22 // These apply to Firefox, (Webkit and IE)
22 // These apply to Firefox, (Webkit and IE)
23 var _keycodes = {
23 var _keycodes = {
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
24 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
25 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
26 's': 83, 't': 84, 'u': 85, 'v': 86, 'w': 87, 'x': 88, 'y': 89, 'z': 90,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
27 '1 !': 49, '2 @': 50, '3 #': 51, '4 $': 52, '5 %': 53, '6 ^': 54,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
30 '\\ |': 220, '\' "': 222,
30 '\\ |': 220, '\' "': 222,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
33 'multiply': 106, 'add': 107, 'subtract': 109, 'decimal': 110, 'divide': 111,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
34 'f1': 112, 'f2': 113, 'f3': 114, 'f4': 115, 'f5': 116, 'f6': 117, 'f7': 118,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
35 'f8': 119, 'f9': 120, 'f11': 122, 'f12': 123, 'f13': 124, 'f14': 125, 'f15': 126,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
36 'backspace': 8, 'tab': 9, 'enter': 13, 'shift': 16, 'ctrl': 17, 'alt': 18,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
37 'meta': 91, 'capslock': 20, 'esc': 27, 'space': 32, 'pageup': 33, 'pagedown': 34,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
38 'end': 35, 'home': 36, 'left': 37, 'up': 38, 'right': 39, 'down': 40,
39 'insert': 45, 'delete': 46, 'numlock': 144,
39 'insert': 45, 'delete': 46, 'numlock': 144,
40 };
40 };
41
41
42 // These apply to Firefox and Opera
42 // These apply to Firefox and Opera
43 var _mozilla_keycodes = {
43 var _mozilla_keycodes = {
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
45 };
45 };
46
46
47 // This apply to Webkit and IE
47 // This apply to Webkit and IE
48 var _ie_keycodes = {
48 var _ie_keycodes = {
49 '; :': 186, '= +': 187, '- _': 189,
49 '; :': 186, '= +': 187, '- _': 189,
50 };
50 };
51
51
52 var browser = IPython.utils.browser[0];
52 var browser = IPython.utils.browser[0];
53 var platform = IPython.utils.platform;
53 var platform = IPython.utils.platform;
54
54
55 if (browser === 'Firefox' || browser === 'Opera') {
55 if (browser === 'Firefox' || browser === 'Opera') {
56 $.extend(_keycodes, _mozilla_keycodes);
56 $.extend(_keycodes, _mozilla_keycodes);
57 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
57 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
58 $.extend(_keycodes, _ie_keycodes);
58 $.extend(_keycodes, _ie_keycodes);
59 }
59 }
60
60
61 var keycodes = {};
61 var keycodes = {};
62 var inv_keycodes = {};
62 var inv_keycodes = {};
63 for (var name in _keycodes) {
63 for (var name in _keycodes) {
64 var names = name.split(' ');
64 var names = name.split(' ');
65 if (names.length === 1) {
65 if (names.length === 1) {
66 var n = names[0];
66 var n = names[0];
67 keycodes[n] = _keycodes[n];
67 keycodes[n] = _keycodes[n];
68 inv_keycodes[_keycodes[n]] = n;
68 inv_keycodes[_keycodes[n]] = n;
69 } else {
69 } else {
70 var primary = names[0];
70 var primary = names[0];
71 var secondary = names[1];
71 var secondary = names[1];
72 keycodes[primary] = _keycodes[name];
72 keycodes[primary] = _keycodes[name];
73 keycodes[secondary] = _keycodes[name];
73 keycodes[secondary] = _keycodes[name];
74 inv_keycodes[_keycodes[name]] = primary;
74 inv_keycodes[_keycodes[name]] = primary;
75 }
75 }
76 }
76 }
77
77
78
78
79 // Default keyboard shortcuts
79 // Default keyboard shortcuts
80
80
81 var default_common_shortcuts = {
81 var default_common_shortcuts = {
82 'shift' : {
82 'shift' : {
83 help : '',
83 help : '',
84 help_index : '',
84 help_index : '',
85 handler : function (event) {
85 handler : function (event) {
86 // ignore shift keydown
86 // ignore shift keydown
87 return true;
87 return true;
88 }
88 }
89 },
89 },
90 'shift+enter' : {
90 'shift+enter' : {
91 help : 'run cell, select below',
91 help : 'run cell, select below',
92 help_index : 'ba',
92 help_index : 'ba',
93 handler : function (event) {
93 handler : function (event) {
94 IPython.notebook.execute_cell_and_select_below();
94 IPython.notebook.execute_cell_and_select_below();
95 return false;
95 return false;
96 }
96 }
97 },
97 },
98 'ctrl+enter' : {
98 'ctrl+enter' : {
99 help : 'run cell',
99 help : 'run cell',
100 help_index : 'bb',
100 help_index : 'bb',
101 handler : function (event) {
101 handler : function (event) {
102 IPython.notebook.execute_cell();
102 IPython.notebook.execute_cell();
103 return false;
103 return false;
104 }
104 }
105 },
105 },
106 'alt+enter' : {
106 'alt+enter' : {
107 help : 'run cell, insert below',
107 help : 'run cell, insert below',
108 help_index : 'bc',
108 help_index : 'bc',
109 handler : function (event) {
109 handler : function (event) {
110 IPython.notebook.execute_cell_and_insert_below();
110 IPython.notebook.execute_cell_and_insert_below();
111 return false;
111 return false;
112 }
112 }
113 }
113 }
114 };
114 };
115
115
116 if (platform === 'MacOS') {
116 if (platform === 'MacOS') {
117 default_common_shortcuts['cmd+s'] =
117 default_common_shortcuts['cmd+s'] =
118 {
118 {
119 help : 'save notebook',
119 help : 'save notebook',
120 help_index : 'fb',
120 help_index : 'fb',
121 handler : function (event) {
121 handler : function (event) {
122 IPython.notebook.save_checkpoint();
122 IPython.notebook.save_checkpoint();
123 event.preventDefault();
123 event.preventDefault();
124 return false;
124 return false;
125 }
125 }
126 };
126 };
127 } else {
127 } else {
128 default_common_shortcuts['ctrl+s'] =
128 default_common_shortcuts['ctrl+s'] =
129 {
129 {
130 help : 'save notebook',
130 help : 'save notebook',
131 help_index : 'fb',
131 help_index : 'fb',
132 handler : function (event) {
132 handler : function (event) {
133 IPython.notebook.save_checkpoint();
133 IPython.notebook.save_checkpoint();
134 event.preventDefault();
134 event.preventDefault();
135 return false;
135 return false;
136 }
136 }
137 };
137 };
138 }
138 }
139
139
140 // Edit mode defaults
140 // Edit mode defaults
141
141
142 var default_edit_shortcuts = {
142 var default_edit_shortcuts = {
143 'esc' : {
143 'esc' : {
144 help : 'command mode',
144 help : 'command mode',
145 help_index : 'aa',
145 help_index : 'aa',
146 handler : function (event) {
146 handler : function (event) {
147 IPython.notebook.command_mode();
147 IPython.notebook.command_mode();
148 IPython.notebook.focus_cell();
148 IPython.notebook.focus_cell();
149 return false;
149 return false;
150 }
150 }
151 },
151 },
152 'ctrl+m' : {
152 'ctrl+m' : {
153 help : 'command mode',
153 help : 'command mode',
154 help_index : 'ab',
154 help_index : 'ab',
155 handler : function (event) {
155 handler : function (event) {
156 IPython.notebook.command_mode();
156 IPython.notebook.command_mode();
157 IPython.notebook.focus_cell();
157 IPython.notebook.focus_cell();
158 return false;
158 return false;
159 }
159 }
160 },
160 },
161 'up' : {
161 'up' : {
162 help : '',
162 help : '',
163 help_index : '',
163 help_index : '',
164 handler : function (event) {
164 handler : function (event) {
165 var cell = IPython.notebook.get_selected_cell();
165 var cell = IPython.notebook.get_selected_cell();
166 if (cell && cell.at_top()) {
166 if (cell && cell.at_top()) {
167 event.preventDefault();
167 event.preventDefault();
168 IPython.notebook.command_mode();
168 IPython.notebook.command_mode();
169 IPython.notebook.select_prev();
169 IPython.notebook.select_prev();
170 IPython.notebook.edit_mode();
170 IPython.notebook.edit_mode();
171 return false;
171 return false;
172 }
172 }
173 }
173 }
174 },
174 },
175 'down' : {
175 'down' : {
176 help : '',
176 help : '',
177 help_index : '',
177 help_index : '',
178 handler : function (event) {
178 handler : function (event) {
179 var cell = IPython.notebook.get_selected_cell();
179 var cell = IPython.notebook.get_selected_cell();
180 if (cell && cell.at_bottom()) {
180 if (cell && cell.at_bottom()) {
181 event.preventDefault();
181 event.preventDefault();
182 IPython.notebook.command_mode();
182 IPython.notebook.command_mode();
183 IPython.notebook.select_next();
183 IPython.notebook.select_next();
184 IPython.notebook.edit_mode();
184 IPython.notebook.edit_mode();
185 return false;
185 return false;
186 }
186 }
187 }
187 }
188 },
188 },
189 'alt+-' : {
189 'alt+-' : {
190 help : 'split cell',
190 help : 'split cell',
191 help_index : 'ea',
191 help_index : 'ea',
192 handler : function (event) {
192 handler : function (event) {
193 IPython.notebook.split_cell();
193 IPython.notebook.split_cell();
194 return false;
194 return false;
195 }
195 }
196 },
196 },
197 'alt+subtract' : {
197 'alt+subtract' : {
198 help : '',
198 help : '',
199 help_index : 'eb',
199 help_index : 'eb',
200 handler : function (event) {
200 handler : function (event) {
201 IPython.notebook.split_cell();
201 IPython.notebook.split_cell();
202 return false;
202 return false;
203 }
203 }
204 },
204 },
205 'tab' : {
205 'tab' : {
206 help : 'indent or complete',
206 help : 'indent or complete',
207 help_index : 'ec',
207 help_index : 'ec',
208 },
208 },
209 'shift+tab' : {
209 'shift+tab' : {
210 help : 'tooltip',
210 help : 'tooltip',
211 help_index : 'ed',
211 help_index : 'ed',
212 },
212 },
213 };
213 };
214
214
215 if (platform === 'MacOS') {
215 if (platform === 'MacOS') {
216 default_edit_shortcuts['cmd+/'] =
216 default_edit_shortcuts['cmd+/'] =
217 {
217 {
218 help : 'toggle comment',
218 help : 'toggle comment',
219 help_index : 'ee'
219 help_index : 'ee'
220 };
220 };
221 default_edit_shortcuts['cmd+]'] =
221 default_edit_shortcuts['cmd+]'] =
222 {
222 {
223 help : 'indent',
223 help : 'indent',
224 help_index : 'ef'
224 help_index : 'ef'
225 };
225 };
226 default_edit_shortcuts['cmd+['] =
226 default_edit_shortcuts['cmd+['] =
227 {
227 {
228 help : 'dedent',
228 help : 'dedent',
229 help_index : 'eg'
229 help_index : 'eg'
230 };
230 };
231 } else {
231 } else {
232 default_edit_shortcuts['ctrl+/'] =
232 default_edit_shortcuts['ctrl+/'] =
233 {
233 {
234 help : 'toggle comment',
234 help : 'toggle comment',
235 help_index : 'ee'
235 help_index : 'ee'
236 };
236 };
237 default_edit_shortcuts['ctrl+]'] =
237 default_edit_shortcuts['ctrl+]'] =
238 {
238 {
239 help : 'indent',
239 help : 'indent',
240 help_index : 'ef'
240 help_index : 'ef'
241 };
241 };
242 default_edit_shortcuts['ctrl+['] =
242 default_edit_shortcuts['ctrl+['] =
243 {
243 {
244 help : 'dedent',
244 help : 'dedent',
245 help_index : 'eg'
245 help_index : 'eg'
246 };
246 };
247 }
247 }
248
248
249 // Command mode defaults
249 // Command mode defaults
250
250
251 var default_command_shortcuts = {
251 var default_command_shortcuts = {
252 'enter' : {
252 'enter' : {
253 help : 'edit mode',
253 help : 'edit mode',
254 help_index : 'aa',
254 help_index : 'aa',
255 handler : function (event) {
255 handler : function (event) {
256 IPython.notebook.edit_mode();
256 IPython.notebook.edit_mode();
257 return false;
257 return false;
258 }
258 }
259 },
259 },
260 'up' : {
260 'up' : {
261 help : 'select previous cell',
261 help : 'select previous cell',
262 help_index : 'da',
262 help_index : 'da',
263 handler : function (event) {
263 handler : function (event) {
264 var index = IPython.notebook.get_selected_index();
264 var index = IPython.notebook.get_selected_index();
265 if (index !== 0 && index !== null) {
265 if (index !== 0 && index !== null) {
266 IPython.notebook.select_prev();
266 IPython.notebook.select_prev();
267 var cell = IPython.notebook.get_selected_cell();
267 var cell = IPython.notebook.get_selected_cell();
268 cell.focus_cell();
268 cell.focus_cell();
269 }
269 }
270 return false;
270 return false;
271 }
271 }
272 },
272 },
273 'down' : {
273 'down' : {
274 help : 'select next cell',
274 help : 'select next cell',
275 help_index : 'db',
275 help_index : 'db',
276 handler : function (event) {
276 handler : function (event) {
277 var index = IPython.notebook.get_selected_index();
277 var index = IPython.notebook.get_selected_index();
278 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
278 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
279 IPython.notebook.select_next();
279 IPython.notebook.select_next();
280 var cell = IPython.notebook.get_selected_cell();
280 var cell = IPython.notebook.get_selected_cell();
281 cell.focus_cell();
281 cell.focus_cell();
282 }
282 }
283 return false;
283 return false;
284 }
284 }
285 },
285 },
286 'k' : {
286 'k' : {
287 help : 'select previous cell',
287 help : 'select previous cell',
288 help_index : 'dc',
288 help_index : 'dc',
289 handler : function (event) {
289 handler : function (event) {
290 var index = IPython.notebook.get_selected_index();
290 var index = IPython.notebook.get_selected_index();
291 if (index !== 0 && index !== null) {
291 if (index !== 0 && index !== null) {
292 IPython.notebook.select_prev();
292 IPython.notebook.select_prev();
293 var cell = IPython.notebook.get_selected_cell();
293 var cell = IPython.notebook.get_selected_cell();
294 cell.focus_cell();
294 cell.focus_cell();
295 }
295 }
296 return false;
296 return false;
297 }
297 }
298 },
298 },
299 'j' : {
299 'j' : {
300 help : 'select next cell',
300 help : 'select next cell',
301 help_index : 'dd',
301 help_index : 'dd',
302 handler : function (event) {
302 handler : function (event) {
303 var index = IPython.notebook.get_selected_index();
303 var index = IPython.notebook.get_selected_index();
304 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
304 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
305 IPython.notebook.select_next();
305 IPython.notebook.select_next();
306 var cell = IPython.notebook.get_selected_cell();
306 var cell = IPython.notebook.get_selected_cell();
307 cell.focus_cell();
307 cell.focus_cell();
308 }
308 }
309 return false;
309 return false;
310 }
310 }
311 },
311 },
312 'x' : {
312 'x' : {
313 help : 'cut cell',
313 help : 'cut cell',
314 help_index : 'ee',
314 help_index : 'ee',
315 handler : function (event) {
315 handler : function (event) {
316 IPython.notebook.cut_cell();
316 IPython.notebook.cut_cell();
317 return false;
317 return false;
318 }
318 }
319 },
319 },
320 'c' : {
320 'c' : {
321 help : 'copy cell',
321 help : 'copy cell',
322 help_index : 'ef',
322 help_index : 'ef',
323 handler : function (event) {
323 handler : function (event) {
324 IPython.notebook.copy_cell();
324 IPython.notebook.copy_cell();
325 return false;
325 return false;
326 }
326 }
327 },
327 },
328 'shift+v' : {
328 'shift+v' : {
329 help : 'paste cell above',
329 help : 'paste cell above',
330 help_index : 'eg',
330 help_index : 'eg',
331 handler : function (event) {
331 handler : function (event) {
332 IPython.notebook.paste_cell_above();
332 IPython.notebook.paste_cell_above();
333 return false;
333 return false;
334 }
334 }
335 },
335 },
336 'v' : {
336 'v' : {
337 help : 'paste cell below',
337 help : 'paste cell below',
338 help_index : 'eh',
338 help_index : 'eh',
339 handler : function (event) {
339 handler : function (event) {
340 IPython.notebook.paste_cell_below();
340 IPython.notebook.paste_cell_below();
341 return false;
341 return false;
342 }
342 }
343 },
343 },
344 'd' : {
344 'd' : {
345 help : 'delete cell (press twice)',
345 help : 'delete cell (press twice)',
346 help_index : 'ej',
346 help_index : 'ej',
347 count: 2,
347 count: 2,
348 handler : function (event) {
348 handler : function (event) {
349 IPython.notebook.delete_cell();
349 IPython.notebook.delete_cell();
350 return false;
350 return false;
351 }
351 }
352 },
352 },
353 'a' : {
353 'a' : {
354 help : 'insert cell above',
354 help : 'insert cell above',
355 help_index : 'ec',
355 help_index : 'ec',
356 handler : function (event) {
356 handler : function (event) {
357 IPython.notebook.insert_cell_above('code');
357 IPython.notebook.insert_cell_above('code');
358 IPython.notebook.select_prev();
358 IPython.notebook.select_prev();
359 IPython.notebook.focus_cell();
359 IPython.notebook.focus_cell();
360 return false;
360 return false;
361 }
361 }
362 },
362 },
363 'b' : {
363 'b' : {
364 help : 'insert cell below',
364 help : 'insert cell below',
365 help_index : 'ed',
365 help_index : 'ed',
366 handler : function (event) {
366 handler : function (event) {
367 IPython.notebook.insert_cell_below('code');
367 IPython.notebook.insert_cell_below('code');
368 IPython.notebook.select_next();
368 IPython.notebook.select_next();
369 IPython.notebook.focus_cell();
369 IPython.notebook.focus_cell();
370 return false;
370 return false;
371 }
371 }
372 },
372 },
373 'y' : {
373 'y' : {
374 help : 'to code',
374 help : 'to code',
375 help_index : 'ca',
375 help_index : 'ca',
376 handler : function (event) {
376 handler : function (event) {
377 IPython.notebook.to_code();
377 IPython.notebook.to_code();
378 return false;
378 return false;
379 }
379 }
380 },
380 },
381 'm' : {
381 'm' : {
382 help : 'to markdown',
382 help : 'to markdown',
383 help_index : 'cb',
383 help_index : 'cb',
384 handler : function (event) {
384 handler : function (event) {
385 IPython.notebook.to_markdown();
385 IPython.notebook.to_markdown();
386 return false;
386 return false;
387 }
387 }
388 },
388 },
389 'r' : {
389 'r' : {
390 help : 'to raw',
390 help : 'to raw',
391 help_index : 'cc',
391 help_index : 'cc',
392 handler : function (event) {
392 handler : function (event) {
393 IPython.notebook.to_raw();
393 IPython.notebook.to_raw();
394 return false;
394 return false;
395 }
395 }
396 },
396 },
397 '1' : {
397 '1' : {
398 help : 'to heading 1',
398 help : 'to heading 1',
399 help_index : 'cd',
399 help_index : 'cd',
400 handler : function (event) {
400 handler : function (event) {
401 IPython.notebook.to_heading(undefined, 1);
401 IPython.notebook.to_heading(undefined, 1);
402 return false;
402 return false;
403 }
403 }
404 },
404 },
405 '2' : {
405 '2' : {
406 help : 'to heading 2',
406 help : 'to heading 2',
407 help_index : 'ce',
407 help_index : 'ce',
408 handler : function (event) {
408 handler : function (event) {
409 IPython.notebook.to_heading(undefined, 2);
409 IPython.notebook.to_heading(undefined, 2);
410 return false;
410 return false;
411 }
411 }
412 },
412 },
413 '3' : {
413 '3' : {
414 help : 'to heading 3',
414 help : 'to heading 3',
415 help_index : 'cf',
415 help_index : 'cf',
416 handler : function (event) {
416 handler : function (event) {
417 IPython.notebook.to_heading(undefined, 3);
417 IPython.notebook.to_heading(undefined, 3);
418 return false;
418 return false;
419 }
419 }
420 },
420 },
421 '4' : {
421 '4' : {
422 help : 'to heading 4',
422 help : 'to heading 4',
423 help_index : 'cg',
423 help_index : 'cg',
424 handler : function (event) {
424 handler : function (event) {
425 IPython.notebook.to_heading(undefined, 4);
425 IPython.notebook.to_heading(undefined, 4);
426 return false;
426 return false;
427 }
427 }
428 },
428 },
429 '5' : {
429 '5' : {
430 help : 'to heading 5',
430 help : 'to heading 5',
431 help_index : 'ch',
431 help_index : 'ch',
432 handler : function (event) {
432 handler : function (event) {
433 IPython.notebook.to_heading(undefined, 5);
433 IPython.notebook.to_heading(undefined, 5);
434 return false;
434 return false;
435 }
435 }
436 },
436 },
437 '6' : {
437 '6' : {
438 help : 'to heading 6',
438 help : 'to heading 6',
439 help_index : 'ci',
439 help_index : 'ci',
440 handler : function (event) {
440 handler : function (event) {
441 IPython.notebook.to_heading(undefined, 6);
441 IPython.notebook.to_heading(undefined, 6);
442 return false;
442 return false;
443 }
443 }
444 },
444 },
445 'o' : {
445 'o' : {
446 help : 'toggle output',
446 help : 'toggle output',
447 help_index : 'gb',
447 help_index : 'gb',
448 handler : function (event) {
448 handler : function (event) {
449 IPython.notebook.toggle_output();
449 IPython.notebook.toggle_output();
450 return false;
450 return false;
451 }
451 }
452 },
452 },
453 'shift+o' : {
453 'shift+o' : {
454 help : 'toggle output scrolling',
454 help : 'toggle output scrolling',
455 help_index : 'gc',
455 help_index : 'gc',
456 handler : function (event) {
456 handler : function (event) {
457 IPython.notebook.toggle_output_scroll();
457 IPython.notebook.toggle_output_scroll();
458 return false;
458 return false;
459 }
459 }
460 },
460 },
461 's' : {
461 's' : {
462 help : 'save notebook',
462 help : 'save notebook',
463 help_index : 'fa',
463 help_index : 'fa',
464 handler : function (event) {
464 handler : function (event) {
465 IPython.notebook.save_checkpoint();
465 IPython.notebook.save_checkpoint();
466 return false;
466 return false;
467 }
467 }
468 },
468 },
469 'ctrl+j' : {
469 'ctrl+j' : {
470 help : 'move cell down',
470 help : 'move cell down',
471 help_index : 'eb',
471 help_index : 'eb',
472 handler : function (event) {
472 handler : function (event) {
473 IPython.notebook.move_cell_down();
473 IPython.notebook.move_cell_down();
474 return false;
474 return false;
475 }
475 }
476 },
476 },
477 'ctrl+k' : {
477 'ctrl+k' : {
478 help : 'move cell up',
478 help : 'move cell up',
479 help_index : 'ea',
479 help_index : 'ea',
480 handler : function (event) {
480 handler : function (event) {
481 IPython.notebook.move_cell_up();
481 IPython.notebook.move_cell_up();
482 return false;
482 return false;
483 }
483 }
484 },
484 },
485 'l' : {
485 'l' : {
486 help : 'toggle line numbers',
486 help : 'toggle line numbers',
487 help_index : 'ga',
487 help_index : 'ga',
488 handler : function (event) {
488 handler : function (event) {
489 IPython.notebook.cell_toggle_line_numbers();
489 IPython.notebook.cell_toggle_line_numbers();
490 return false;
490 return false;
491 }
491 }
492 },
492 },
493 'i' : {
493 'i' : {
494 help : 'interrupt kernel (press twice)',
494 help : 'interrupt kernel (press twice)',
495 help_index : 'ha',
495 help_index : 'ha',
496 count: 2,
496 count: 2,
497 handler : function (event) {
497 handler : function (event) {
498 IPython.notebook.kernel.interrupt();
498 IPython.notebook.kernel.interrupt();
499 return false;
499 return false;
500 }
500 }
501 },
501 },
502 '0' : {
502 '0' : {
503 help : 'restart kernel (press twice)',
503 help : 'restart kernel (press twice)',
504 help_index : 'hb',
504 help_index : 'hb',
505 count: 2,
505 count: 2,
506 handler : function (event) {
506 handler : function (event) {
507 IPython.notebook.restart_kernel();
507 IPython.notebook.restart_kernel();
508 return false;
508 return false;
509 }
509 }
510 },
510 },
511 'h' : {
511 'h' : {
512 help : 'keyboard shortcuts',
512 help : 'keyboard shortcuts',
513 help_index : 'ge',
513 help_index : 'ge',
514 handler : function (event) {
514 handler : function (event) {
515 IPython.quick_help.show_keyboard_shortcuts();
515 IPython.quick_help.show_keyboard_shortcuts();
516 return false;
516 return false;
517 }
517 }
518 },
518 },
519 'z' : {
519 'z' : {
520 help : 'undo last delete',
520 help : 'undo last delete',
521 help_index : 'ei',
521 help_index : 'ei',
522 handler : function (event) {
522 handler : function (event) {
523 IPython.notebook.undelete_cell();
523 IPython.notebook.undelete_cell();
524 return false;
524 return false;
525 }
525 }
526 },
526 },
527 'shift+m' : {
527 'shift+m' : {
528 help : 'merge cell below',
528 help : 'merge cell below',
529 help_index : 'ek',
529 help_index : 'ek',
530 handler : function (event) {
530 handler : function (event) {
531 IPython.notebook.merge_cell_below();
531 IPython.notebook.merge_cell_below();
532 return false;
532 return false;
533 }
533 }
534 },
534 },
535 'q' : {
535 'q' : {
536 help : 'close pager',
536 help : 'close pager',
537 help_index : 'gd',
537 help_index : 'gd',
538 handler : function (event) {
538 handler : function (event) {
539 IPython.pager.collapse();
539 IPython.pager.collapse();
540 return false;
540 return false;
541 }
541 }
542 },
542 },
543 };
543 };
544
544
545
545
546 // Shortcut manager class
546 // Shortcut manager class
547
547
548 var ShortcutManager = function (delay) {
548 var ShortcutManager = function (delay) {
549 this._shortcuts = {};
549 this._shortcuts = {};
550 this._counts = {};
550 this._counts = {};
551 this._timers = {};
551 this._timers = {};
552 this.delay = delay || 800; // delay in milliseconds
552 this.delay = delay || 800; // delay in milliseconds
553 };
553 };
554
554
555 ShortcutManager.prototype.help = function () {
555 ShortcutManager.prototype.help = function () {
556 var help = [];
556 var help = [];
557 for (var shortcut in this._shortcuts) {
557 for (var shortcut in this._shortcuts) {
558 var help_string = this._shortcuts[shortcut].help;
558 var help_string = this._shortcuts[shortcut].help;
559 var help_index = this._shortcuts[shortcut].help_index;
559 var help_index = this._shortcuts[shortcut].help_index;
560 if (help_string) {
560 if (help_string) {
561 if (platform === 'MacOS') {
561 if (platform === 'MacOS') {
562 shortcut = shortcut.replace('meta', 'cmd');
562 shortcut = shortcut.replace('meta', 'cmd');
563 }
563 }
564 help.push({
564 help.push({
565 shortcut: shortcut,
565 shortcut: shortcut,
566 help: help_string,
566 help: help_string,
567 help_index: help_index}
567 help_index: help_index}
568 );
568 );
569 }
569 }
570 }
570 }
571 help.sort(function (a, b) {
571 help.sort(function (a, b) {
572 if (a.help_index > b.help_index)
572 if (a.help_index > b.help_index)
573 return 1;
573 return 1;
574 if (a.help_index < b.help_index)
574 if (a.help_index < b.help_index)
575 return -1;
575 return -1;
576 return 0;
576 return 0;
577 });
577 });
578 return help;
578 return help;
579 };
579 };
580
580
581 ShortcutManager.prototype.normalize_key = function (key) {
581 ShortcutManager.prototype.normalize_key = function (key) {
582 return inv_keycodes[keycodes[key]];
582 return inv_keycodes[keycodes[key]];
583 };
583 };
584
584
585 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
585 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
586 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
586 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
587 shortcut = shortcut.replace('cmd', 'meta').toLowerCase();
587 shortcut = shortcut.replace('cmd', 'meta').toLowerCase();
588 var values = shortcut.split("+");
588 var values = shortcut.split("+");
589 if (values.length === 1) {
589 if (values.length === 1) {
590 return this.normalize_key(values[0]);
590 return this.normalize_key(values[0]);
591 } else {
591 } else {
592 var modifiers = values.slice(0,-1);
592 var modifiers = values.slice(0,-1);
593 var key = this.normalize_key(values[values.length-1]);
593 var key = this.normalize_key(values[values.length-1]);
594 modifiers.sort();
594 modifiers.sort();
595 return modifiers.join('+') + '+' + key;
595 return modifiers.join('+') + '+' + key;
596 }
596 }
597 };
597 };
598
598
599 ShortcutManager.prototype.event_to_shortcut = function (event) {
599 ShortcutManager.prototype.event_to_shortcut = function (event) {
600 // Convert a jQuery keyboard event to a strong based keyboard shortcut
600 // Convert a jQuery keyboard event to a strong based keyboard shortcut
601 var shortcut = '';
601 var shortcut = '';
602 var key = inv_keycodes[event.which];
602 var key = inv_keycodes[event.which];
603 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
603 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
604 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
604 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
605 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
605 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
606 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
606 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
607 shortcut += key;
607 shortcut += key;
608 return shortcut;
608 return shortcut;
609 };
609 };
610
610
611 ShortcutManager.prototype.clear_shortcuts = function () {
611 ShortcutManager.prototype.clear_shortcuts = function () {
612 this._shortcuts = {};
612 this._shortcuts = {};
613 };
613 };
614
614
615 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
615 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
616 if (typeof(data) === 'function') {
616 if (typeof(data) === 'function') {
617 data = {help: '', help_index: '', handler: data};
617 data = {help: '', help_index: '', handler: data};
618 }
618 }
619 data.help_index = data.help_index || '';
619 data.help_index = data.help_index || '';
620 data.help = data.help || '';
620 data.help = data.help || '';
621 data.count = data.count || 1;
621 data.count = data.count || 1;
622 if (data.help_index === '') {
622 if (data.help_index === '') {
623 data.help_index = 'zz';
623 data.help_index = 'zz';
624 }
624 }
625 shortcut = this.normalize_shortcut(shortcut);
625 shortcut = this.normalize_shortcut(shortcut);
626 this._counts[shortcut] = 0;
626 this._counts[shortcut] = 0;
627 this._shortcuts[shortcut] = data;
627 this._shortcuts[shortcut] = data;
628 };
628 };
629
629
630 ShortcutManager.prototype.add_shortcuts = function (data) {
630 ShortcutManager.prototype.add_shortcuts = function (data) {
631 for (var shortcut in data) {
631 for (var shortcut in data) {
632 this.add_shortcut(shortcut, data[shortcut]);
632 this.add_shortcut(shortcut, data[shortcut]);
633 }
633 }
634 };
634 };
635
635
636 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
636 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
637 shortcut = this.normalize_shortcut(shortcut);
637 shortcut = this.normalize_shortcut(shortcut);
638 delete this._counts[shortcut];
638 delete this._counts[shortcut];
639 delete this._shortcuts[shortcut];
639 delete this._shortcuts[shortcut];
640 };
640 };
641
641
642 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
642 ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
643 var that = this;
643 var that = this;
644 var c = this._counts;
644 var c = this._counts;
645 var t = this._timers;
645 var t = this._timers;
646 var timer = null;
646 var timer = null;
647 if (c[shortcut] === data.count-1) {
647 if (c[shortcut] === data.count-1) {
648 c[shortcut] = 0;
648 c[shortcut] = 0;
649 timer = t[shortcut];
649 timer = t[shortcut];
650 if (timer) {clearTimeout(timer); delete t[shortcut];}
650 if (timer) {clearTimeout(timer); delete t[shortcut];}
651 return data.handler(event);
651 return data.handler(event);
652 } else {
652 } else {
653 c[shortcut] = c[shortcut] + 1;
653 c[shortcut] = c[shortcut] + 1;
654 timer = setTimeout(function () {
654 timer = setTimeout(function () {
655 c[shortcut] = 0;
655 c[shortcut] = 0;
656 }, that.delay);
656 }, that.delay);
657 t[shortcut] = timer;
657 t[shortcut] = timer;
658 }
658 }
659 return false;
659 return false;
660 };
660 };
661
661
662 ShortcutManager.prototype.call_handler = function (event) {
662 ShortcutManager.prototype.call_handler = function (event) {
663 var shortcut = this.event_to_shortcut(event);
663 var shortcut = this.event_to_shortcut(event);
664 var data = this._shortcuts[shortcut];
664 var data = this._shortcuts[shortcut];
665 if (data) {
665 if (data) {
666 var handler = data.handler;
666 var handler = data.handler;
667 if (handler) {
667 if (handler) {
668 if (data.count === 1) {
668 if (data.count === 1) {
669 return handler(event);
669 return handler(event);
670 } else if (data.count > 1) {
670 } else if (data.count > 1) {
671 return this.count_handler(shortcut, event, data);
671 return this.count_handler(shortcut, event, data);
672 }
672 }
673 }
673 }
674 }
674 }
675 return true;
675 return true;
676 };
676 };
677
677
678
678
679 // Main keyboard manager for the notebook
679 // Main keyboard manager for the notebook
680
680
681 var KeyboardManager = function () {
681 var KeyboardManager = function () {
682 this.mode = 'command';
682 this.mode = 'command';
683 this.enabled = true;
683 this.enabled = true;
684 this.bind_events();
684 this.bind_events();
685 this.command_shortcuts = new ShortcutManager();
685 this.command_shortcuts = new ShortcutManager();
686 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
686 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
687 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
687 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
688 this.edit_shortcuts = new ShortcutManager();
688 this.edit_shortcuts = new ShortcutManager();
689 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
689 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
690 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
690 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
691 };
691 };
692
692
693 KeyboardManager.prototype.bind_events = function () {
693 KeyboardManager.prototype.bind_events = function () {
694 var that = this;
694 var that = this;
695 $(document).keydown(function (event) {
695 $(document).keydown(function (event) {
696 return that.handle_keydown(event);
696 return that.handle_keydown(event);
697 });
697 });
698 };
698 };
699
699
700 KeyboardManager.prototype.handle_keydown = function (event) {
700 KeyboardManager.prototype.handle_keydown = function (event) {
701 var notebook = IPython.notebook;
701 var notebook = IPython.notebook;
702
702
703 if (event.which === keycodes.esc) {
703 if (event.which === keycodes.esc) {
704 // Intercept escape at highest level to avoid closing
704 // Intercept escape at highest level to avoid closing
705 // websocket connection with firefox
705 // websocket connection with firefox
706 event.preventDefault();
706 event.preventDefault();
707 }
707 }
708
708
709 if (!this.enabled) {
709 if (!this.enabled) {
710 if (event.which === keycodes.esc) {
710 if (event.which === keycodes.esc) {
711 // ESC
711 // ESC
712 notebook.command_mode();
712 notebook.command_mode();
713 return false;
713 return false;
714 }
714 }
715 return true;
715 return true;
716 }
716 }
717
717
718 if (this.mode === 'edit') {
718 if (this.mode === 'edit') {
719 return this.edit_shortcuts.call_handler(event);
719 return this.edit_shortcuts.call_handler(event);
720 } else if (this.mode === 'command') {
720 } else if (this.mode === 'command') {
721 return this.command_shortcuts.call_handler(event);
721 return this.command_shortcuts.call_handler(event);
722 }
722 }
723 return true;
723 return true;
724 };
724 };
725
725
726 KeyboardManager.prototype.edit_mode = function () {
726 KeyboardManager.prototype.edit_mode = function () {
727 this.last_mode = this.mode;
727 this.last_mode = this.mode;
728 this.mode = 'edit';
728 this.mode = 'edit';
729 };
729 };
730
730
731 KeyboardManager.prototype.command_mode = function () {
731 KeyboardManager.prototype.command_mode = function () {
732 this.last_mode = this.mode;
732 this.last_mode = this.mode;
733 this.mode = 'command';
733 this.mode = 'command';
734 };
734 };
735
735
736 KeyboardManager.prototype.enable = function () {
736 KeyboardManager.prototype.enable = function () {
737 this.enabled = true;
737 this.enabled = true;
738 };
738 };
739
739
740 KeyboardManager.prototype.disable = function () {
740 KeyboardManager.prototype.disable = function () {
741 this.enabled = false;
741 this.enabled = false;
742 };
742 };
743
743
744 KeyboardManager.prototype.register_events = function (e) {
744 KeyboardManager.prototype.register_events = function (e) {
745 var that = this;
745 var that = this;
746 var handle_focus = function () {
746 var handle_focus = function () {
747 that.disable();
747 that.disable();
748 };
748 };
749 var handle_blur = function () {
749 var handle_blur = function () {
750 that.enable();
750 that.enable();
751 };
751 };
752 e.on('focusin', handle_focus);
752 e.on('focusin', handle_focus);
753 e.on('focusout', handle_blur);
753 e.on('focusout', handle_blur);
754 // TODO: Very strange. The focusout event does not seem fire for the
754 // // TODO: Very strange. The focusout event does not seem fire for the
755 // bootstrap textboxes on FF25&26...
755 // // bootstrap textboxes on FF25&26...
756 e.find('input').blur(handle_blur);
756 // e.find('input').blur(handle_blur);
757 e.on('DOMNodeInserted', function (event) {
757 // e.on('DOMNodeInserted', function (event) {
758 var target = $(event.target);
758 // var target = $(event.target);
759 if (target.is('input')) {
759 // if (target.is('input')) {
760 target.blur(handle_blur);
760 // target.blur(handle_blur);
761 } else {
761 // } else {
762 target.find('input').blur(handle_blur);
762 // target.find('input').blur(handle_blur);
763 }
763 // }
764 });
764 // });
765 // There are times (raw_input) where we remove the element from the DOM before
765 // There are times (raw_input) where we remove the element from the DOM before
766 // focusout is called. In this case we bind to the remove event of jQueryUI,
766 // focusout is called. In this case we bind to the remove event of jQueryUI,
767 // which gets triggered upon removal, iff it is focused at the time.
767 // which gets triggered upon removal, iff it is focused at the time.
768 e.on('remove', function () {
768 e.on('remove', function () {
769 if (IPython.utils.is_focused(e[0])) {
769 if (IPython.utils.is_focused(e[0])) {
770 that.enable();
770 that.enable();
771 }
771 }
772 });
772 });
773 };
773 };
774
774
775
775
776 IPython.keycodes = keycodes;
776 IPython.keycodes = keycodes;
777 IPython.inv_keycodes = inv_keycodes;
777 IPython.inv_keycodes = inv_keycodes;
778 IPython.default_common_shortcuts = default_common_shortcuts;
778 IPython.default_common_shortcuts = default_common_shortcuts;
779 IPython.default_edit_shortcuts = default_edit_shortcuts;
779 IPython.default_edit_shortcuts = default_edit_shortcuts;
780 IPython.default_command_shortcuts = default_command_shortcuts;
780 IPython.default_command_shortcuts = default_command_shortcuts;
781 IPython.ShortcutManager = ShortcutManager;
781 IPython.ShortcutManager = ShortcutManager;
782 IPython.KeyboardManager = KeyboardManager;
782 IPython.KeyboardManager = KeyboardManager;
783
783
784 return IPython;
784 return IPython;
785
785
786 }(IPython));
786 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now