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