##// END OF EJS Templates
Merge pull request #3605 from ellisonbg/newux...
Brian E. Granger -
r14101:59235449 merge
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (683 lines changed) Show them Hide them
@@ -0,0 +1,683 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Keyboard management
10 //============================================================================
11
12 var IPython = (function (IPython) {
13 "use strict";
14
15 // Setup global keycodes and inverse keycodes.
16
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"
19 // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
20 // but have minor differences.
21
22 // These apply to Firefox, (Webkit and IE)
23 var _keycodes = {
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,
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,
28 '7 &': 55, '8 *': 56, '9 (': 57, '0 )': 48,
29 '[ {': 219, '] }': 221, '` ~': 192, ', <': 188, '. >': 190, '/ ?': 191,
30 '\\ |': 220, '\' "': 222,
31 'numpad0': 96, 'numpad1': 97, 'numpad2': 98, 'numpad3': 99, 'numpad4': 100,
32 'numpad5': 101, 'numpad6': 102, 'numpad7': 103, 'numpad8': 104, 'numpad9': 105,
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,
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,
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,
39 'insert': 45, 'delete': 46, 'numlock': 144,
40 };
41
42 // These apply to Firefox and Opera
43 var _mozilla_keycodes = {
44 '; :': 59, '= +': 61, '- _': 173, 'meta': 224
45 }
46
47 // This apply to Webkit and IE
48 var _ie_keycodes = {
49 '; :': 186, '= +': 187, '- _': 189,
50 }
51
52 var browser = IPython.utils.browser[0];
53
54 if (browser === 'Firefox' || browser === 'Opera') {
55 $.extend(_keycodes, _mozilla_keycodes);
56 } else if (browser === 'Safari' || browser === 'Chrome' || browser === 'MSIE') {
57 $.extend(_keycodes, _ie_keycodes);
58 }
59
60 var keycodes = {};
61 var inv_keycodes = {};
62 for (var name in _keycodes) {
63 var names = name.split(' ');
64 if (names.length === 1) {
65 var n = names[0]
66 keycodes[n] = _keycodes[n]
67 inv_keycodes[_keycodes[n]] = n
68 } else {
69 var primary = names[0];
70 var secondary = names[1];
71 keycodes[primary] = _keycodes[name]
72 keycodes[secondary] = _keycodes[name]
73 inv_keycodes[_keycodes[name]] = primary
74 }
75 }
76
77
78 // Default keyboard shortcuts
79
80 var default_common_shortcuts = {
81 'meta+s' : {
82 help : 'save notebook',
83 help_index : 'fb',
84 handler : function (event) {
85 IPython.notebook.save_checkpoint();
86 event.preventDefault();
87 return false;
88 }
89 },
90 'ctrl+s' : {
91 help : 'save notebook',
92 help_index : 'fc',
93 handler : function (event) {
94 IPython.notebook.save_checkpoint();
95 event.preventDefault();
96 return false;
97 }
98 },
99 'shift' : {
100 help : '',
101 help_index : '',
102 handler : function (event) {
103 // ignore shift keydown
104 return true;
105 }
106 },
107 'shift+enter' : {
108 help : 'run cell',
109 help_index : 'ba',
110 handler : function (event) {
111 IPython.notebook.execute_cell();
112 return false;
113 }
114 },
115 'ctrl+enter' : {
116 help : 'run cell, select below',
117 help_index : 'bb',
118 handler : function (event) {
119 IPython.notebook.execute_cell_and_select_below();
120 return false;
121 }
122 },
123 'alt+enter' : {
124 help : 'run cell, insert below',
125 help_index : 'bc',
126 handler : function (event) {
127 IPython.notebook.execute_cell_and_insert_below();
128 return false;
129 }
130 }
131 }
132
133 // Edit mode defaults
134
135 var default_edit_shortcuts = {
136 'esc' : {
137 help : 'command mode',
138 help_index : 'aa',
139 handler : function (event) {
140 IPython.notebook.command_mode();
141 IPython.notebook.focus_cell();
142 return false;
143 }
144 },
145 'ctrl+m' : {
146 help : 'command mode',
147 help_index : 'ab',
148 handler : function (event) {
149 IPython.notebook.command_mode();
150 IPython.notebook.focus_cell();
151 return false;
152 }
153 },
154 'up' : {
155 help : '',
156 help_index : '',
157 handler : function (event) {
158 var cell = IPython.notebook.get_selected_cell();
159 if (cell && cell.at_top()) {
160 event.preventDefault();
161 IPython.notebook.command_mode()
162 IPython.notebook.select_prev();
163 IPython.notebook.edit_mode();
164 return false;
165 };
166 }
167 },
168 'down' : {
169 help : '',
170 help_index : '',
171 handler : function (event) {
172 var cell = IPython.notebook.get_selected_cell();
173 if (cell && cell.at_bottom()) {
174 event.preventDefault();
175 IPython.notebook.command_mode()
176 IPython.notebook.select_next();
177 IPython.notebook.edit_mode();
178 return false;
179 };
180 }
181 },
182 'alt+-' : {
183 help : 'split cell',
184 help_index : 'ea',
185 handler : function (event) {
186 IPython.notebook.split_cell();
187 return false;
188 }
189 },
190 'alt+subtract' : {
191 help : '',
192 help_index : 'eb',
193 handler : function (event) {
194 IPython.notebook.split_cell();
195 return false;
196 }
197 },
198 }
199
200 // Command mode defaults
201
202 var default_command_shortcuts = {
203 'enter' : {
204 help : 'edit mode',
205 help_index : 'aa',
206 handler : function (event) {
207 IPython.notebook.edit_mode();
208 return false;
209 }
210 },
211 'up' : {
212 help : 'select previous cell',
213 help_index : 'da',
214 handler : function (event) {
215 var index = IPython.notebook.get_selected_index();
216 if (index !== 0 && index !== null) {
217 IPython.notebook.select_prev();
218 var cell = IPython.notebook.get_selected_cell();
219 cell.focus_cell();
220 };
221 return false;
222 }
223 },
224 'down' : {
225 help : 'select next cell',
226 help_index : 'db',
227 handler : function (event) {
228 var index = IPython.notebook.get_selected_index();
229 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
230 IPython.notebook.select_next();
231 var cell = IPython.notebook.get_selected_cell();
232 cell.focus_cell();
233 };
234 return false;
235 }
236 },
237 'k' : {
238 help : 'select previous cell',
239 help_index : 'dc',
240 handler : function (event) {
241 var index = IPython.notebook.get_selected_index();
242 if (index !== 0 && index !== null) {
243 IPython.notebook.select_prev();
244 var cell = IPython.notebook.get_selected_cell();
245 cell.focus_cell();
246 };
247 return false;
248 }
249 },
250 'j' : {
251 help : 'select next cell',
252 help_index : 'dd',
253 handler : function (event) {
254 var index = IPython.notebook.get_selected_index();
255 if (index !== (IPython.notebook.ncells()-1) && index !== null) {
256 IPython.notebook.select_next();
257 var cell = IPython.notebook.get_selected_cell();
258 cell.focus_cell();
259 };
260 return false;
261 }
262 },
263 'x' : {
264 help : 'cut cell',
265 help_index : 'ee',
266 handler : function (event) {
267 IPython.notebook.cut_cell();
268 return false;
269 }
270 },
271 'c' : {
272 help : 'copy cell',
273 help_index : 'ef',
274 handler : function (event) {
275 IPython.notebook.copy_cell();
276 return false;
277 }
278 },
279 'v' : {
280 help : 'paste cell below',
281 help_index : 'eg',
282 handler : function (event) {
283 IPython.notebook.paste_cell_below();
284 return false;
285 }
286 },
287 'd' : {
288 help : 'delete cell (press twice)',
289 help_index : 'ei',
290 handler : function (event) {
291 var dc = IPython.keyboard_manager._delete_count;
292 if (dc === 0) {
293 IPython.keyboard_manager._delete_count = 1;
294 setTimeout(function () {
295 IPython.keyboard_manager._delete_count = 0;
296 }, 800);
297 } else if (dc === 1) {
298 IPython.notebook.delete_cell();
299 IPython.keyboard_manager._delete_count = 0;
300 }
301 return false;
302 }
303 },
304 'a' : {
305 help : 'insert cell above',
306 help_index : 'ec',
307 handler : function (event) {
308 IPython.notebook.insert_cell_above('code');
309 IPython.notebook.select_prev();
310 IPython.notebook.focus_cell();
311 return false;
312 }
313 },
314 'b' : {
315 help : 'insert cell below',
316 help_index : 'ed',
317 handler : function (event) {
318 IPython.notebook.insert_cell_below('code');
319 IPython.notebook.select_next();
320 IPython.notebook.focus_cell();
321 return false;
322 }
323 },
324 'y' : {
325 help : 'to code',
326 help_index : 'ca',
327 handler : function (event) {
328 IPython.notebook.to_code();
329 return false;
330 }
331 },
332 'm' : {
333 help : 'to markdown',
334 help_index : 'cb',
335 handler : function (event) {
336 IPython.notebook.to_markdown();
337 return false;
338 }
339 },
340 't' : {
341 help : 'to raw',
342 help_index : 'cc',
343 handler : function (event) {
344 IPython.notebook.to_raw();
345 return false;
346 }
347 },
348 '1' : {
349 help : 'to heading 1',
350 help_index : 'cd',
351 handler : function (event) {
352 IPython.notebook.to_heading(undefined, 1);
353 return false;
354 }
355 },
356 '2' : {
357 help : 'to heading 2',
358 help_index : 'ce',
359 handler : function (event) {
360 IPython.notebook.to_heading(undefined, 2);
361 return false;
362 }
363 },
364 '3' : {
365 help : 'to heading 3',
366 help_index : 'cf',
367 handler : function (event) {
368 IPython.notebook.to_heading(undefined, 3);
369 return false;
370 }
371 },
372 '4' : {
373 help : 'to heading 4',
374 help_index : 'cg',
375 handler : function (event) {
376 IPython.notebook.to_heading(undefined, 4);
377 return false;
378 }
379 },
380 '5' : {
381 help : 'to heading 5',
382 help_index : 'ch',
383 handler : function (event) {
384 IPython.notebook.to_heading(undefined, 5);
385 return false;
386 }
387 },
388 '6' : {
389 help : 'to heading 6',
390 help_index : 'ci',
391 handler : function (event) {
392 IPython.notebook.to_heading(undefined, 6);
393 return false;
394 }
395 },
396 'o' : {
397 help : 'toggle output',
398 help_index : 'gb',
399 handler : function (event) {
400 IPython.notebook.toggle_output();
401 return false;
402 }
403 },
404 'shift+o' : {
405 help : 'toggle output',
406 help_index : 'gc',
407 handler : function (event) {
408 IPython.notebook.toggle_output_scroll();
409 return false;
410 }
411 },
412 's' : {
413 help : 'save notebook',
414 help_index : 'fa',
415 handler : function (event) {
416 IPython.notebook.save_checkpoint();
417 return false;
418 }
419 },
420 'ctrl+j' : {
421 help : 'move cell down',
422 help_index : 'eb',
423 handler : function (event) {
424 IPython.notebook.move_cell_down();
425 return false;
426 }
427 },
428 'ctrl+k' : {
429 help : 'move cell up',
430 help_index : 'ea',
431 handler : function (event) {
432 IPython.notebook.move_cell_up();
433 return false;
434 }
435 },
436 'l' : {
437 help : 'toggle line numbers',
438 help_index : 'ga',
439 handler : function (event) {
440 IPython.notebook.cell_toggle_line_numbers();
441 return false;
442 }
443 },
444 'i' : {
445 help : 'interrupt kernel',
446 help_index : 'ha',
447 handler : function (event) {
448 IPython.notebook.kernel.interrupt();
449 return false;
450 }
451 },
452 '.' : {
453 help : 'restart kernel',
454 help_index : 'hb',
455 handler : function (event) {
456 IPython.notebook.restart_kernel();
457 return false;
458 }
459 },
460 'h' : {
461 help : 'keyboard shortcuts',
462 help_index : 'gd',
463 handler : function (event) {
464 IPython.quick_help.show_keyboard_shortcuts();
465 return false;
466 }
467 },
468 'z' : {
469 help : 'undo last delete',
470 help_index : 'eh',
471 handler : function (event) {
472 IPython.notebook.undelete_cell();
473 return false;
474 }
475 },
476 'shift+=' : {
477 help : 'merge cell below',
478 help_index : 'ej',
479 handler : function (event) {
480 IPython.notebook.merge_cell_below();
481 return false;
482 }
483 },
484 }
485
486
487 // Shortcut manager class
488
489 var ShortcutManager = function () {
490 this._shortcuts = {}
491 }
492
493 ShortcutManager.prototype.help = function () {
494 var help = [];
495 for (var shortcut in this._shortcuts) {
496 var help_string = this._shortcuts[shortcut]['help'];
497 var help_index = this._shortcuts[shortcut]['help_index'];
498 if (help_string) {
499 help.push({
500 shortcut: shortcut,
501 help: help_string,
502 help_index: help_index}
503 );
504 }
505 }
506 help.sort(function (a, b) {
507 if (a.help_index > b.help_index)
508 return 1;
509 if (a.help_index < b.help_index)
510 return -1;
511 return 0;
512 });
513 return help;
514 }
515
516 ShortcutManager.prototype.normalize_key = function (key) {
517 return inv_keycodes[keycodes[key]];
518 }
519
520 ShortcutManager.prototype.normalize_shortcut = function (shortcut) {
521 // Sort a sequence of + separated modifiers into the order alt+ctrl+meta+shift
522 var values = shortcut.split("+");
523 if (values.length === 1) {
524 return this.normalize_key(values[0])
525 } else {
526 var modifiers = values.slice(0,-1);
527 var key = this.normalize_key(values[values.length-1]);
528 modifiers.sort();
529 return modifiers.join('+') + '+' + key;
530 }
531 }
532
533 ShortcutManager.prototype.event_to_shortcut = function (event) {
534 // Convert a jQuery keyboard event to a strong based keyboard shortcut
535 var shortcut = '';
536 var key = inv_keycodes[event.which]
537 if (event.altKey && key !== 'alt') {shortcut += 'alt+';}
538 if (event.ctrlKey && key !== 'ctrl') {shortcut += 'ctrl+';}
539 if (event.metaKey && key !== 'meta') {shortcut += 'meta+';}
540 if (event.shiftKey && key !== 'shift') {shortcut += 'shift+';}
541 shortcut += key;
542 return shortcut
543 }
544
545 ShortcutManager.prototype.clear_shortcuts = function () {
546 this._shortcuts = {};
547 }
548
549 ShortcutManager.prototype.add_shortcut = function (shortcut, data) {
550 if (typeof(data) === 'function') {
551 data = {help: '', help_index: '', handler: data}
552 }
553 data.help_index = data.help_index || '';
554 data.help = data.help || '';
555 if (data.help_index === '') {
556 data.help_index = 'zz';
557 }
558 shortcut = this.normalize_shortcut(shortcut);
559 this._shortcuts[shortcut] = data;
560 }
561
562 ShortcutManager.prototype.add_shortcuts = function (data) {
563 for (var shortcut in data) {
564 this.add_shortcut(shortcut, data[shortcut]);
565 }
566 }
567
568 ShortcutManager.prototype.remove_shortcut = function (shortcut) {
569 shortcut = this.normalize_shortcut(shortcut);
570 delete this._shortcuts[shortcut];
571 }
572
573 ShortcutManager.prototype.call_handler = function (event) {
574 var shortcut = this.event_to_shortcut(event);
575 var data = this._shortcuts[shortcut];
576 if (data !== undefined) {
577 var handler = data['handler'];
578 if (handler !== undefined) {
579 return handler(event);
580 }
581 }
582 return true;
583 }
584
585
586
587 // Main keyboard manager for the notebook
588
589 var KeyboardManager = function () {
590 this.mode = 'command';
591 this.enabled = true;
592 this._delete_count = 0;
593 this.bind_events();
594 this.command_shortcuts = new ShortcutManager();
595 this.command_shortcuts.add_shortcuts(default_common_shortcuts);
596 this.command_shortcuts.add_shortcuts(default_command_shortcuts);
597 this.edit_shortcuts = new ShortcutManager();
598 this.edit_shortcuts.add_shortcuts(default_common_shortcuts);
599 this.edit_shortcuts.add_shortcuts(default_edit_shortcuts);
600 };
601
602 KeyboardManager.prototype.bind_events = function () {
603 var that = this;
604 $(document).keydown(function (event) {
605 return that.handle_keydown(event);
606 });
607 };
608
609 KeyboardManager.prototype.handle_keydown = function (event) {
610 var notebook = IPython.notebook;
611
612 if (event.which === keycodes['esc']) {
613 // Intercept escape at highest level to avoid closing
614 // websocket connection with firefox
615 event.preventDefault();
616 }
617
618 if (!this.enabled) {
619 if (event.which === keycodes['esc']) {
620 // ESC
621 notebook.command_mode();
622 return false;
623 }
624 return true;
625 }
626
627 if (this.mode === 'edit') {
628 return this.edit_shortcuts.call_handler(event);
629 } else if (this.mode === 'command') {
630 return this.command_shortcuts.call_handler(event);
631 }
632 return true;
633 }
634
635 KeyboardManager.prototype.edit_mode = function () {
636 this.last_mode = this.mode;
637 this.mode = 'edit';
638 }
639
640 KeyboardManager.prototype.command_mode = function () {
641 this.last_mode = this.mode;
642 this.mode = 'command';
643 }
644
645 KeyboardManager.prototype.enable = function () {
646 this.enabled = true;
647 }
648
649 KeyboardManager.prototype.disable = function () {
650 this.enabled = false;
651 }
652
653 KeyboardManager.prototype.register_events = function (e) {
654 var that = this;
655 e.on('focusin', function () {
656 that.command_mode();
657 that.disable();
658 });
659 e.on('focusout', function () {
660 that.command_mode();
661 that.enable();
662 });
663 // There are times (raw_input) where we remove the element from the DOM before
664 // focusout is called. In this case we bind to the remove event of jQueryUI,
665 // which gets triggered upon removal.
666 e.on('remove', function () {
667 that.command_mode();
668 that.enable();
669 });
670 }
671
672
673 IPython.keycodes = keycodes;
674 IPython.inv_keycodes = inv_keycodes;
675 IPython.default_common_shortcuts = default_common_shortcuts;
676 IPython.default_edit_shortcuts = default_edit_shortcuts;
677 IPython.default_command_shortcuts = default_command_shortcuts;
678 IPython.ShortcutManager = ShortcutManager;
679 IPython.KeyboardManager = KeyboardManager;
680
681 return IPython;
682
683 }(IPython));
@@ -0,0 +1,421 b''
1 {
2 "metadata": {
3 "name": ""
4 },
5 "nbformat": 3,
6 "nbformat_minor": 0,
7 "worksheets": [
8 {
9 "cells": [
10 {
11 "cell_type": "heading",
12 "level": 1,
13 "metadata": {},
14 "source": [
15 "User Interface"
16 ]
17 },
18 {
19 "cell_type": "markdown",
20 "metadata": {},
21 "source": [
22 "This notebook describes the user interface of the IPython Notebook. This includes both mouse and keyboard based navigation and interaction.\n",
23 "\n",
24 "<div class=\"alert\" style=\"margin: 10px\">\n",
25 "As of IPython 2.0, the user interface has changed significantly. Because of this we highly recommend existing users to review this information after upgrading to IPython 2.0. All new users of IPython should review this information as well.\n",
26 "</div>"
27 ]
28 },
29 {
30 "cell_type": "heading",
31 "level": 2,
32 "metadata": {},
33 "source": [
34 "Modal editor"
35 ]
36 },
37 {
38 "cell_type": "markdown",
39 "metadata": {},
40 "source": [
41 "Starting with IPython 2.0, the IPython Notebook has a modal user interface. This means that the keyboard does different things depending on which mode the Notebook is in. There are two modes: edit mode and command mode."
42 ]
43 },
44 {
45 "cell_type": "heading",
46 "level": 3,
47 "metadata": {},
48 "source": [
49 "Edit mode"
50 ]
51 },
52 {
53 "cell_type": "markdown",
54 "metadata": {},
55 "source": [
56 "Edit mode is indicated by a green cell border and a prompt showing in the editor area:\n",
57 "\n",
58 "<img src=\"images/edit_mode.png\">\n",
59 "\n",
60 "When a cell is in edit mode, you can type into the cell, like a normal text editor.\n",
61 "\n",
62 "<div class=\"alert alert-success\" style=\"margin: 10px\">\n",
63 "Enter edit mode by pressing `enter` or using the mouse to click on a cell's editor area.\n",
64 "</div>"
65 ]
66 },
67 {
68 "cell_type": "heading",
69 "level": 3,
70 "metadata": {},
71 "source": [
72 "Command mode"
73 ]
74 },
75 {
76 "cell_type": "markdown",
77 "metadata": {},
78 "source": [
79 "Command mode is indicated by a grey cell border:\n",
80 "\n",
81 "<img src=\"images/command_mode.png\">\n",
82 "\n",
83 "When you are in command mode, you are able to edit the notebook as a whole, but not type into individual cells. Most importantly, in command mode, the keyboard is mapped to a set of shortcuts that let you perform notebook and cell actions efficiently. For example, if you are in command mode and you press `c`, you will copy the current cell - no modifier is needed.\n",
84 "\n",
85 "<div class=\"alert alert-error\" style=\"margin: 10px\">\n",
86 "Don't try to type into a cell in command mode; unexpected things will happen!\n",
87 "</div>\n",
88 "\n",
89 "<div class=\"alert alert-success\" style=\"margin: 10px\">\n",
90 "Enter command mode by pressing `esc` or using the mouse to click *outside* a cell's editor area.\n",
91 "</div>"
92 ]
93 },
94 {
95 "cell_type": "heading",
96 "level": 2,
97 "metadata": {},
98 "source": [
99 "Mouse navigation"
100 ]
101 },
102 {
103 "cell_type": "markdown",
104 "metadata": {},
105 "source": [
106 "All navigation and actions in the Notebook are available using the mouse through the menubar and toolbar, which are both above the main Notebook area:\n",
107 "\n",
108 "<img src=\"images/menubar_toolbar.png\">"
109 ]
110 },
111 {
112 "cell_type": "markdown",
113 "metadata": {},
114 "source": [
115 "The first idea of mouse based navigation is that **cells can be selected by clicking on them.** The currently selected cell gets a grey or green border depending on whether the notebook is in edit or command mode. If you click inside a cell's editor area, you will enter edit mode. If you click on the prompt or output area of a cell you will enter command mode.\n",
116 "\n",
117 "If you are running this notebook in a live session (not on http://nbviewer.ipython.org) try selecting different cells and going between edit and command mode. Try typing into a cell."
118 ]
119 },
120 {
121 "cell_type": "markdown",
122 "metadata": {},
123 "source": [
124 "The second idea of mouse based navigation is that **cell actions usually apply to the currently selected cell**. Thus if you want to run the code in a cell, you would select it and click the \"Play\" button in the toolbar or the \"Cell:Run\" menu item. Similarly, to copy a cell you would select it and click the \"Copy\" button in the toolbar or the \"Edit:Copy\" menu item. With this simple pattern, you should be able to do most everything you need with the mouse.\n",
125 "\n",
126 "Markdown and heading cells have one other state that can be modified with the mouse. These cells can either be rendered or unrendered. When they are rendered, you will see a nice formatted representation of the cell's contents. When they are unrendered, you will see the raw text source of the cell. To render the selected cell with the mouse, click the \"Play\" button in the toolbar or the \"Cell:Run\" menu item. To unrender the selected cell, double click on the cell."
127 ]
128 },
129 {
130 "cell_type": "heading",
131 "level": 2,
132 "metadata": {},
133 "source": [
134 "Keyboard Navigation"
135 ]
136 },
137 {
138 "cell_type": "markdown",
139 "metadata": {},
140 "source": [
141 "The modal user interface of the IPython Notebook has been optimized for efficient keyboard usage. This is made possible by having two different sets of keyboard shortcuts: one set that is active in edit mode and another in command mode.\n",
142 "\n",
143 "The most important keyboard shortcuts are `enter`, which enters edit mode, and `esc`, which enters command mode.\n",
144 "\n",
145 "In edit mode, most of the keyboard is dedicated to typing into the cell's editor. Thus, in edit mode there are relatively few shortcuts:"
146 ]
147 },
148 {
149 "cell_type": "markdown",
150 "metadata": {},
151 "source": [
152 "The `display_edit_shortcuts()` function used here is defined in the [Utilities section](#Utilities) at the bottom of this notebook."
153 ]
154 },
155 {
156 "cell_type": "code",
157 "collapsed": false,
158 "input": [
159 "display_edit_shortcuts()"
160 ],
161 "language": "python",
162 "metadata": {},
163 "outputs": [
164 {
165 "html": [
166 "<div class=\"hbox\"><div class=\"box-flex0\"><div class=\"quickhelp\"><span class=\"shortcut_key\">esc</span><span class=\"shortcut_descr\"> : command mode</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">ctrl+m</span><span class=\"shortcut_descr\"> : command mode</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">shift+enter</span><span class=\"shortcut_descr\"> : run cell</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">ctrl+enter</span><span class=\"shortcut_descr\"> : run cell, select below</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">alt+enter</span><span class=\"shortcut_descr\"> : run cell, insert below</span></div></div><div class=\"box-flex0\"><div class=\"quickhelp\"><span class=\"shortcut_key\">up</span><span class=\"shortcut_descr\"> : select previous cell</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">down</span><span class=\"shortcut_descr\"> : select next cell</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">alt+-</span><span class=\"shortcut_descr\"> : split cell</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">meta+s</span><span class=\"shortcut_descr\"> : save notebook</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">ctrl+s</span><span class=\"shortcut_descr\"> : save notebook</span></div></div></div>"
167 ],
168 "output_type": "display_data"
169 },
170 {
171 "javascript": [
172 "var help = IPython.quick_help.build_edit_help();\n",
173 "help.children().first().remove();\n",
174 "this.append_output({output_type: 'display_data', html: help.html()});"
175 ],
176 "metadata": {},
177 "output_type": "display_data",
178 "text": [
179 "<IPython.core.display.Javascript at 0x10e441250>"
180 ]
181 }
182 ],
183 "prompt_number": 14
184 },
185 {
186 "cell_type": "markdown",
187 "metadata": {},
188 "source": [
189 "There are two other keyboard shortcuts in edit mode that are not listed here:\n",
190 "\n",
191 "* `tab`: trigger \"tab\" completion\n",
192 "* `shift+tab`: open the tooltip"
193 ]
194 },
195 {
196 "cell_type": "markdown",
197 "metadata": {},
198 "source": [
199 "In command mode, the entire keyboard is available for shortcuts:"
200 ]
201 },
202 {
203 "cell_type": "code",
204 "collapsed": false,
205 "input": [
206 "display_command_shortcuts()"
207 ],
208 "language": "python",
209 "metadata": {},
210 "outputs": [
211 {
212 "html": [
213 "<div class=\"hbox\"><div class=\"box-flex0\"><div class=\"quickhelp\"><span class=\"shortcut_key\">enter</span><span class=\"shortcut_descr\"> : edit mode</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">shift+enter</span><span class=\"shortcut_descr\"> : run cell</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">ctrl+enter</span><span class=\"shortcut_descr\"> : run cell, select below</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">alt+enter</span><span class=\"shortcut_descr\"> : run cell, insert below</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">y</span><span class=\"shortcut_descr\"> : to code</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">m</span><span class=\"shortcut_descr\"> : to markdown</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">t</span><span class=\"shortcut_descr\"> : to raw</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">1</span><span class=\"shortcut_descr\"> : to heading 1</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">2</span><span class=\"shortcut_descr\"> : to heading 2</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">3</span><span class=\"shortcut_descr\"> : to heading 3</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">4</span><span class=\"shortcut_descr\"> : to heading 4</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">5</span><span class=\"shortcut_descr\"> : to heading 5</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">6</span><span class=\"shortcut_descr\"> : to heading 6</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">up</span><span class=\"shortcut_descr\"> : select previous cell</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">down</span><span class=\"shortcut_descr\"> : select next cell</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">k</span><span class=\"shortcut_descr\"> : select previous cell</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">j</span><span class=\"shortcut_descr\"> : select next cell</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">ctrl+k</span><span class=\"shortcut_descr\"> : move cell up</span></div></div><div class=\"box-flex0\"><div class=\"quickhelp\"><span class=\"shortcut_key\">ctrl+j</span><span class=\"shortcut_descr\"> : move cell down</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">a</span><span class=\"shortcut_descr\"> : insert cell above</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">b</span><span class=\"shortcut_descr\"> : insert cell below</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">x</span><span class=\"shortcut_descr\"> : cut cell</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">c</span><span class=\"shortcut_descr\"> : copy cell</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">v</span><span class=\"shortcut_descr\"> : paste cell below</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">z</span><span class=\"shortcut_descr\"> : undo last delete</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">d</span><span class=\"shortcut_descr\"> : delete cell (press twice)</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">shift+=</span><span class=\"shortcut_descr\"> : merge cell below</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">s</span><span class=\"shortcut_descr\"> : save notebook</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">meta+s</span><span class=\"shortcut_descr\"> : save notebook</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">ctrl+s</span><span class=\"shortcut_descr\"> : save notebook</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">l</span><span class=\"shortcut_descr\"> : toggle line numbers</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">o</span><span class=\"shortcut_descr\"> : toggle output</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">shift+o</span><span class=\"shortcut_descr\"> : toggle output</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">h</span><span class=\"shortcut_descr\"> : keyboard shortcuts</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">i</span><span class=\"shortcut_descr\"> : interrupt kernel</span></div><div class=\"quickhelp\"><span class=\"shortcut_key\">.</span><span class=\"shortcut_descr\"> : restart kernel</span></div></div></div>"
214 ],
215 "output_type": "display_data"
216 },
217 {
218 "javascript": [
219 "var help = IPython.quick_help.build_command_help();\n",
220 "help.children().first().remove();\n",
221 "this.append_output({output_type: 'display_data', html: help.html()});"
222 ],
223 "metadata": {},
224 "output_type": "display_data",
225 "text": [
226 "<IPython.core.display.Javascript at 0x10e441410>"
227 ]
228 }
229 ],
230 "prompt_number": 15
231 },
232 {
233 "cell_type": "markdown",
234 "metadata": {},
235 "source": [
236 "Here the rough order in which we recommend learning the command mode shortcuts:\n",
237 "\n",
238 "1. Basic navigation: `enter`, `shift-enter`, `up/k`, `down/j`\n",
239 "2. Saving the notebook: `s`\n",
240 "2. Cell types: `y`, `m`, `1-6`, `t`\n",
241 "3. Cell creation and movement: `a`, `b`, `ctrl+k`, `ctrl+j`\n",
242 "4. Cell editing: `x`, `c`, `v`, `d`, `z`, `shift+=`\n",
243 "5. Kernel operations: `i`, `.`"
244 ]
245 },
246 {
247 "cell_type": "heading",
248 "level": 2,
249 "metadata": {},
250 "source": [
251 "Keyboard shortcut customization"
252 ]
253 },
254 {
255 "cell_type": "markdown",
256 "metadata": {},
257 "source": [
258 "Starting with IPython 2.0 keyboard shortcuts in command and edit mode are fully customizable. These customizations are made using the IPython JavaScript API. Here is an example that makes the `r` key available for running a cell:"
259 ]
260 },
261 {
262 "cell_type": "code",
263 "collapsed": false,
264 "input": [
265 "%%javascript\n",
266 "\n",
267 "IPython.keyboard_manager.command_shortcuts.add_shortcut('r', {\n",
268 " help : 'run cell',\n",
269 " help_index : 'zz',\n",
270 " handler : function (event) {\n",
271 " IPython.notebook.execute_cell();\n",
272 " return false;\n",
273 " }}\n",
274 ");"
275 ],
276 "language": "python",
277 "metadata": {},
278 "outputs": [
279 {
280 "javascript": [
281 "\n",
282 "IPython.keyboard_manager.command_shortcuts.add_shortcut('r', {\n",
283 " help : 'run cell',\n",
284 " help_index : 'aa',\n",
285 " handler : function (event) {\n",
286 " IPython.notebook.execute_cell();\n",
287 " return false;\n",
288 " }}\n",
289 ");"
290 ],
291 "metadata": {},
292 "output_type": "display_data",
293 "text": [
294 "<IPython.core.display.Javascript at 0x1019ba990>"
295 ]
296 }
297 ],
298 "prompt_number": 6
299 },
300 {
301 "cell_type": "markdown",
302 "metadata": {},
303 "source": [
304 "There are a couple of points to mention about this API:\n",
305 "\n",
306 "* The `help_index` field is used to sort the shortcuts in the Keyboard Shortcuts help dialog. It defaults to `zz`.\n",
307 "* When a handler returns `false` it indicates that the event should stop propagating and the default action should not be performed. For further details about the `event` object or event handling, see the jQuery docs.\n",
308 "* If you don't need a `help` or `help_index` field, you can simply pass a function as the second argument to `add_shortcut`."
309 ]
310 },
311 {
312 "cell_type": "code",
313 "collapsed": false,
314 "input": [
315 "%%javascript\n",
316 "\n",
317 "IPython.keyboard_manager.command_shortcuts.add_shortcut('r', function (event) {\n",
318 " IPython.notebook.execute_cell();\n",
319 " return false;\n",
320 "});"
321 ],
322 "language": "python",
323 "metadata": {},
324 "outputs": [
325 {
326 "javascript": [
327 "\n",
328 "IPython.keyboard_manager.command_shortcuts.add_shortcut('r', function (event) {\n",
329 " IPython.notebook.execute_cell();\n",
330 " return false;\n",
331 "});"
332 ],
333 "metadata": {},
334 "output_type": "display_data",
335 "text": [
336 "<IPython.core.display.Javascript at 0x1019baf90>"
337 ]
338 }
339 ],
340 "prompt_number": 11
341 },
342 {
343 "cell_type": "markdown",
344 "metadata": {},
345 "source": [
346 "Likewise, to remove a shortcut, use `remove_shortcut`:"
347 ]
348 },
349 {
350 "cell_type": "code",
351 "collapsed": false,
352 "input": [
353 "%%javascript\n",
354 "\n",
355 "IPython.keyboard_manager.command_shortcuts.remove_shortcut('r');"
356 ],
357 "language": "python",
358 "metadata": {},
359 "outputs": [
360 {
361 "javascript": [
362 "\n",
363 "IPython.keyboard_manager.command_shortcuts.remove_shortcut('r');"
364 ],
365 "metadata": {},
366 "output_type": "display_data",
367 "text": [
368 "<IPython.core.display.Javascript at 0x1019ba950>"
369 ]
370 }
371 ],
372 "prompt_number": 8
373 },
374 {
375 "cell_type": "markdown",
376 "metadata": {},
377 "source": [
378 "If you want your keyboard shortcuts to be active for all of your notebooks, put the above API calls into your `custom.js` file."
379 ]
380 },
381 {
382 "cell_type": "heading",
383 "level": 2,
384 "metadata": {},
385 "source": [
386 "Utilities"
387 ]
388 },
389 {
390 "cell_type": "markdown",
391 "metadata": {},
392 "source": [
393 "We use the following functions to generate the keyboard shortcut listings above."
394 ]
395 },
396 {
397 "cell_type": "code",
398 "collapsed": false,
399 "input": [
400 "from IPython.display import Javascript, display\n",
401 "\n",
402 "t = \"\"\"var help = IPython.quick_help.build_{0}_help();\n",
403 "help.children().first().remove();\n",
404 "this.append_output({{output_type: 'display_data', html: help.html()}});\"\"\"\n",
405 "\n",
406 "def display_command_shortcuts():\n",
407 " display(Javascript(t.format('command')))\n",
408 "\n",
409 "def display_edit_shortcuts():\n",
410 " display(Javascript(t.format('edit'))) "
411 ],
412 "language": "python",
413 "metadata": {},
414 "outputs": [],
415 "prompt_number": 2
416 }
417 ],
418 "metadata": {}
419 }
420 ]
421 } No newline at end of file
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
@@ -65,13 +65,17 b' IPython.dialog = (function (IPython) {'
65 65 dialog.remove();
66 66 });
67 67 }
68 if (options.reselect_cell !== false) {
69 dialog.on("hidden", function () {
70 if (IPython.notebook) {
71 var cell = IPython.notebook.get_selected_cell();
72 if (cell) cell.select();
73 }
74 });
68 dialog.on("hidden", function () {
69 if (IPython.notebook) {
70 var cell = IPython.notebook.get_selected_cell();
71 if (cell) cell.select();
72 IPython.keyboard_manager.enable();
73 IPython.keyboard_manager.command_mode();
74 }
75 });
76
77 if (IPython.keyboard_manager) {
78 IPython.keyboard_manager.disable();
75 79 }
76 80
77 81 return dialog.modal(options);
@@ -455,6 +455,26 b' IPython.utils = (function (IPython) {'
455 455 return M;
456 456 })();
457 457
458 var is_or_has = function (a, b) {
459 // Is b a child of a or a itself?
460 return a.has(b).length !==0 || a.is(b);
461 }
462
463 var is_focused = function (e) {
464 // Is element e, or one of its children focused?
465 e = $(e);
466 var target = $(document.activeElement);
467 if (target.length > 0) {
468 if (is_or_has(e, target)) {
469 return true;
470 } else {
471 return false;
472 }
473 } else {
474 return false;
475 }
476 }
477
458 478
459 479 return {
460 480 regex_split : regex_split,
@@ -475,7 +495,9 b' IPython.utils = (function (IPython) {'
475 495 encode_uri_components : encode_uri_components,
476 496 splitext : splitext,
477 497 always_new : always_new,
478 browser : browser
498 browser : browser,
499 is_or_has : is_or_has,
500 is_focused : is_focused
479 501 };
480 502
481 503 }(IPython));
@@ -39,6 +39,8 b' var IPython = (function (IPython) {'
39 39 this.placeholder = options.placeholder || '';
40 40 this.read_only = options.cm_config.readOnly;
41 41 this.selected = false;
42 this.rendered = false;
43 this.mode = 'command';
42 44 this.metadata = {};
43 45 // load this from metadata later ?
44 46 this.user_highlight = 'auto';
@@ -60,6 +62,7 b' var IPython = (function (IPython) {'
60 62 if (this.element !== null) {
61 63 this.element.data("cell", this);
62 64 this.bind_events();
65 this.init_classes();
63 66 }
64 67 };
65 68
@@ -97,6 +100,26 b' var IPython = (function (IPython) {'
97 100 Cell.prototype.create_element = function () {
98 101 };
99 102
103 Cell.prototype.init_classes = function () {
104 // Call after this.element exists to initialize the css classes
105 // related to selected, rendered and mode.
106 if (this.selected) {
107 this.element.addClass('selected');
108 } else {
109 this.element.addClass('unselected');
110 }
111 if (this.rendered) {
112 this.element.addClass('rendered');
113 } else {
114 this.element.addClass('unrendered');
115 }
116 if (this.mode === 'edit') {
117 this.element.addClass('edit_mode');
118 } else {
119 this.element.addClass('command_mode');
120 }
121 }
122
100 123
101 124 /**
102 125 * Subclasses can implement override bind_events.
@@ -108,20 +131,41 b' var IPython = (function (IPython) {'
108 131 var that = this;
109 132 // We trigger events so that Cell doesn't have to depend on Notebook.
110 133 that.element.click(function (event) {
111 if (that.selected === false) {
134 if (!that.selected) {
112 135 $([IPython.events]).trigger('select.Cell', {'cell':that});
113 }
136 };
114 137 });
115 138 that.element.focusin(function (event) {
116 if (that.selected === false) {
139 if (!that.selected) {
117 140 $([IPython.events]).trigger('select.Cell', {'cell':that});
118 }
141 };
119 142 });
120 143 if (this.code_mirror) {
121 144 this.code_mirror.on("change", function(cm, change) {
122 145 $([IPython.events]).trigger("set_dirty.Notebook", {value: true});
123 146 });
124 147 }
148 if (this.code_mirror) {
149 this.code_mirror.on('focus', function(cm, change) {
150 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
151 });
152 }
153 if (this.code_mirror) {
154 this.code_mirror.on('blur', function(cm, change) {
155 if (that.mode === 'edit') {
156 setTimeout(function () {
157 var isf = IPython.utils.is_focused;
158 var trigger = true;
159 if (isf('div#tooltip') || isf('div.completions')) {
160 trigger = false;
161 }
162 if (trigger) {
163 $([IPython.events]).trigger('command_mode.Cell', {cell: that});
164 }
165 }, 1);
166 }
167 });
168 }
125 169 };
126 170
127 171 /**
@@ -129,47 +173,126 b' var IPython = (function (IPython) {'
129 173 * @method typeset
130 174 */
131 175 Cell.prototype.typeset = function () {
132 if (window.MathJax){
176 if (window.MathJax) {
133 177 var cell_math = this.element.get(0);
134 178 MathJax.Hub.Queue(["Typeset", MathJax.Hub, cell_math]);
135 179 }
136 180 };
137 181
138 182 /**
139 * should be triggerd when cell is selected
183 * handle cell level logic when a cell is selected
140 184 * @method select
185 * @return is the action being taken
141 186 */
142 187 Cell.prototype.select = function () {
143 this.element.addClass('selected');
144 this.selected = true;
188 if (!this.selected) {
189 this.element.addClass('selected');
190 this.element.removeClass('unselected');
191 this.selected = true;
192 return true;
193 } else {
194 return false;
195 }
145 196 };
146 197
147
148 198 /**
149 * should be triggerd when cell is unselected
199 * handle cell level logic when a cell is unselected
150 200 * @method unselect
201 * @return is the action being taken
151 202 */
152 203 Cell.prototype.unselect = function () {
153 this.element.removeClass('selected');
154 this.selected = false;
204 if (this.selected) {
205 this.element.addClass('unselected');
206 this.element.removeClass('selected');
207 this.selected = false;
208 return true;
209 } else {
210 return false;
211 }
155 212 };
156 213
157 214 /**
158 * should be overritten by subclass
159 * @method get_text
215 * handle cell level logic when a cell is rendered
216 * @method render
217 * @return is the action being taken
160 218 */
161 Cell.prototype.get_text = function () {
219 Cell.prototype.render = function () {
220 if (!this.rendered) {
221 this.element.addClass('rendered');
222 this.element.removeClass('unrendered');
223 this.rendered = true;
224 return true;
225 } else {
226 return false;
227 }
162 228 };
163 229
164 230 /**
165 * should be overritten by subclass
166 * @method set_text
167 * @param {string} text
231 * handle cell level logic when a cell is unrendered
232 * @method unrender
233 * @return is the action being taken
168 234 */
169 Cell.prototype.set_text = function (text) {
235 Cell.prototype.unrender = function () {
236 if (this.rendered) {
237 this.element.addClass('unrendered');
238 this.element.removeClass('rendered');
239 this.rendered = false;
240 return true;
241 } else {
242 return false;
243 }
244 };
245
246 /**
247 * enter the command mode for the cell
248 * @method command_mode
249 * @return is the action being taken
250 */
251 Cell.prototype.command_mode = function () {
252 if (this.mode !== 'command') {
253 this.element.addClass('command_mode');
254 this.element.removeClass('edit_mode');
255 this.mode = 'command';
256 return true;
257 } else {
258 return false;
259 }
170 260 };
171 261
172 262 /**
263 * enter the edit mode for the cell
264 * @method command_mode
265 * @return is the action being taken
266 */
267 Cell.prototype.edit_mode = function () {
268 if (this.mode !== 'edit') {
269 this.element.addClass('edit_mode');
270 this.element.removeClass('command_mode');
271 this.mode = 'edit';
272 return true;
273 } else {
274 return false;
275 }
276 }
277
278 /**
279 * Focus the cell in the DOM sense
280 * @method focus_cell
281 */
282 Cell.prototype.focus_cell = function () {
283 this.element.focus();
284 }
285
286 /**
287 * Focus the editor area so a user can type
288 * @method focus_editor
289 */
290 Cell.prototype.focus_editor = function () {
291 this.refresh();
292 this.code_mirror.focus();
293 }
294
295 /**
173 296 * Refresh codemirror instance
174 297 * @method refresh
175 298 */
@@ -177,20 +300,19 b' var IPython = (function (IPython) {'
177 300 this.code_mirror.refresh();
178 301 };
179 302
180
181 303 /**
182 304 * should be overritten by subclass
183 * @method edit
184 **/
185 Cell.prototype.edit = function () {
305 * @method get_text
306 */
307 Cell.prototype.get_text = function () {
186 308 };
187 309
188
189 310 /**
190 311 * should be overritten by subclass
191 * @method render
192 **/
193 Cell.prototype.render = function () {
312 * @method set_text
313 * @param {string} text
314 */
315 Cell.prototype.set_text = function (text) {
194 316 };
195 317
196 318 /**
@@ -74,7 +74,7 b' var IPython = (function (IPython) {'
74 74
75 75
76 76 var cm_overwrite_options = {
77 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
77 onKeyEvent: $.proxy(this.handle_keyevent,this)
78 78 };
79 79
80 80 options = this.mergeopt(CodeCell, options, {cm_config:cm_overwrite_options});
@@ -139,6 +139,27 b' var IPython = (function (IPython) {'
139 139 this.completer = new IPython.Completer(this);
140 140 };
141 141
142 /** @method bind_events */
143 CodeCell.prototype.bind_events = function () {
144 IPython.Cell.prototype.bind_events.apply(this);
145 var that = this;
146
147 this.element.focusout(
148 function() { that.auto_highlight(); }
149 );
150 };
151
152 CodeCell.prototype.handle_keyevent = function (editor, event) {
153
154 // console.log('CM', this.mode, event.which, event.type)
155
156 if (this.mode === 'command') {
157 return true;
158 } else if (this.mode === 'edit') {
159 return this.handle_codemirror_keyevent(editor, event);
160 }
161 };
162
142 163 /**
143 164 * This method gets called in CodeMirror's onKeyDown/onKeyPress
144 165 * handlers and is used to provide custom key handling. Its return
@@ -151,8 +172,9 b' var IPython = (function (IPython) {'
151 172 var that = this;
152 173 // whatever key is pressed, first, cancel the tooltip request before
153 174 // they are sent, and remove tooltip if any, except for tab again
175 var tooltip_closed = null;
154 176 if (event.type === 'keydown' && event.which != key.TAB ) {
155 IPython.tooltip.remove_and_cancel_tooltip();
177 tooltip_closed = IPython.tooltip.remove_and_cancel_tooltip();
156 178 }
157 179
158 180 var cur = editor.getCursor();
@@ -160,7 +182,7 b' var IPython = (function (IPython) {'
160 182 this.auto_highlight();
161 183 }
162 184
163 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey)) {
185 if (event.keyCode === key.ENTER && (event.shiftKey || event.ctrlKey || event.altKey)) {
164 186 // Always ignore shift-enter in CodeMirror as we handle it.
165 187 return true;
166 188 } else if (event.which === 40 && event.type === 'keypress' && IPython.tooltip.time_before_tooltip >= 0) {
@@ -179,8 +201,32 b' var IPython = (function (IPython) {'
179 201 } else {
180 202 return true;
181 203 }
182 } else if (event.which === key.ESC) {
183 return IPython.tooltip.remove_and_cancel_tooltip(true);
204 } else if (event.which === key.ESC && event.type === 'keydown') {
205 // First see if the tooltip is active and if so cancel it.
206 if (tooltip_closed) {
207 // The call to remove_and_cancel_tooltip above in L177 doesn't pass
208 // force=true. Because of this it won't actually close the tooltip
209 // if it is in sticky mode. Thus, we have to check again if it is open
210 // and close it with force=true.
211 if (!IPython.tooltip._hidden) {
212 IPython.tooltip.remove_and_cancel_tooltip(true);
213 }
214 // If we closed the tooltip, don't let CM or the global handlers
215 // handle this event.
216 event.stop();
217 return true;
218 }
219 if (that.code_mirror.options.keyMap === "vim-insert") {
220 // vim keyMap is active and in insert mode. In this case we leave vim
221 // insert mode, but remain in notebook edit mode.
222 // Let' CM handle this event and prevent global handling.
223 event.stop();
224 return false;
225 } else {
226 // vim keyMap is not active. Leave notebook edit mode.
227 // Don't let CM handle the event, defer to global handling.
228 return true;
229 }
184 230 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
185 231 // If we are not at the bottom, let CM handle the down arrow and
186 232 // prevent the global keydown handler from handling it.
@@ -190,7 +236,7 b' var IPython = (function (IPython) {'
190 236 } else {
191 237 return true;
192 238 }
193 } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) {
239 } else if (event.keyCode === key.TAB && event.type === 'keydown' && event.shiftKey) {
194 240 if (editor.somethingSelected()){
195 241 var anchor = editor.getCursor("anchor");
196 242 var head = editor.getCursor("head");
@@ -225,7 +271,6 b' var IPython = (function (IPython) {'
225 271 return false;
226 272 };
227 273
228
229 274 // Kernel related calls.
230 275
231 276 CodeCell.prototype.set_kernel = function (kernel) {
@@ -304,15 +349,32 b' var IPython = (function (IPython) {'
304 349 // Basic cell manipulation.
305 350
306 351 CodeCell.prototype.select = function () {
307 IPython.Cell.prototype.select.apply(this);
308 this.code_mirror.refresh();
309 this.code_mirror.focus();
310 this.auto_highlight();
311 // We used to need an additional refresh() after the focus, but
312 // it appears that this has been fixed in CM. This bug would show
313 // up on FF when a newly loaded markdown cell was edited.
352 var cont = IPython.Cell.prototype.select.apply(this);
353 if (cont) {
354 this.code_mirror.refresh();
355 this.auto_highlight();
356 }
357 return cont;
358 };
359
360 CodeCell.prototype.render = function () {
361 var cont = IPython.Cell.prototype.render.apply(this);
362 // Always execute, even if we are already in the rendered state
363 return cont;
364 };
365
366 CodeCell.prototype.unrender = function () {
367 // CodeCell is always rendered
368 return false;
314 369 };
315 370
371 CodeCell.prototype.edit_mode = function () {
372 var cont = IPython.Cell.prototype.edit_mode.apply(this);
373 if (cont) {
374 this.focus_editor();
375 }
376 return cont;
377 }
316 378
317 379 CodeCell.prototype.select_all = function () {
318 380 var start = {line: 0, ch: 0};
@@ -218,6 +218,8 b' var IPython = (function (IPython) {'
218 218 this.complete = $('<div/>').addClass('completions');
219 219 this.complete.attr('id', 'complete');
220 220
221 // Currently webkit doesn't use the size attr correctly. See:
222 // https://code.google.com/p/chromium/issues/detail?id=4579
221 223 this.sel = $('<select style="width: auto"/>')
222 224 .attr('multiple', 'true')
223 225 .attr('size', Math.min(10, this.raw_result.length));
@@ -255,6 +257,7 b' var IPython = (function (IPython) {'
255 257 this.build_gui_list(this.raw_result);
256 258
257 259 this.sel.focus();
260 IPython.keyboard_manager.disable();
258 261 // Opera sometimes ignores focusing a freshly created node
259 262 if (window.opera) setTimeout(function () {
260 263 if (!this.done) this.sel.focus();
@@ -279,6 +282,7 b' var IPython = (function (IPython) {'
279 282 if (this.done) return;
280 283 this.done = true;
281 284 $('.completions').remove();
285 IPython.keyboard_manager.enable();
282 286 }
283 287
284 288 Completer.prototype.pick = function () {
@@ -62,6 +62,7 b' function (marked) {'
62 62 IPython.quick_help = new IPython.QuickHelp();
63 63 IPython.login_widget = new IPython.LoginWidget('span#login_widget',{baseProjectUrl:baseProjectUrl});
64 64 IPython.notebook = new IPython.Notebook('div#notebook',{baseProjectUrl:baseProjectUrl, notebookPath:notebookPath, notebookName:notebookName});
65 IPython.keyboard_manager = new IPython.KeyboardManager();
65 66 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
66 67 IPython.menubar = new IPython.MenuBar('#menubar',{baseProjectUrl:baseProjectUrl, notebookPath: notebookPath})
67 68 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container')
@@ -41,6 +41,8 b' var IPython = (function (IPython) {'
41 41 icon : 'icon-plus-sign',
42 42 callback : function () {
43 43 IPython.notebook.insert_cell_below('code');
44 IPython.notebook.select_next();
45 IPython.notebook.focus_cell();
44 46 }
45 47 }
46 48 ],'insert_above_below');
@@ -98,7 +100,7 b' var IPython = (function (IPython) {'
98 100 label : 'Run Cell',
99 101 icon : 'icon-play',
100 102 callback : function () {
101 IPython.notebook.execute_selected_cell();
103 IPython.notebook.execute_cell();
102 104 }
103 105 },
104 106 {
@@ -161,7 +161,7 b' var IPython = (function (IPython) {'
161 161 IPython.notebook.delete_cell();
162 162 });
163 163 this.element.find('#undelete_cell').click(function () {
164 IPython.notebook.undelete();
164 IPython.notebook.undelete_cell();
165 165 });
166 166 this.element.find('#split_cell').click(function () {
167 167 IPython.notebook.split_cell();
@@ -200,16 +200,21 b' var IPython = (function (IPython) {'
200 200 // Insert
201 201 this.element.find('#insert_cell_above').click(function () {
202 202 IPython.notebook.insert_cell_above('code');
203 IPython.notebook.select_prev();
203 204 });
204 205 this.element.find('#insert_cell_below').click(function () {
205 206 IPython.notebook.insert_cell_below('code');
207 IPython.notebook.select_next();
206 208 });
207 209 // Cell
208 210 this.element.find('#run_cell').click(function () {
209 IPython.notebook.execute_selected_cell();
211 IPython.notebook.execute_cell();
212 });
213 this.element.find('#run_cell_select_below').click(function () {
214 IPython.notebook.execute_cell_and_select_below();
210 215 });
211 this.element.find('#run_cell_in_place').click(function () {
212 IPython.notebook.execute_selected_cell({terminal:true});
216 this.element.find('#run_cell_insert_below').click(function () {
217 IPython.notebook.execute_cell_and_insert_below();
213 218 });
214 219 this.element.find('#run_all_cells').click(function () {
215 220 IPython.notebook.execute_all_cells();
This diff has been collapsed as it changes many lines, (538 lines changed) Show them Hide them
@@ -13,7 +13,6 b' var IPython = (function (IPython) {'
13 13 "use strict";
14 14
15 15 var utils = IPython.utils;
16 var key = IPython.utils.keycodes;
17 16
18 17 /**
19 18 * A notebook contains and manages cells.
@@ -39,6 +38,9 b' var IPython = (function (IPython) {'
39 38 this.undelete_index = null;
40 39 this.undelete_below = false;
41 40 this.paste_enabled = false;
41 // It is important to start out in command mode to match the intial mode
42 // of the KeyboardManager.
43 this.mode = 'command';
42 44 this.set_dirty(false);
43 45 this.metadata = {};
44 46 this._checkpoint_after_save = false;
@@ -50,7 +52,6 b' var IPython = (function (IPython) {'
50 52 this.minimum_autosave_interval = 120000;
51 53 // single worksheet for now
52 54 this.worksheet_metadata = {};
53 this.control_key_active = false;
54 55 this.notebook_name_blacklist_re = /[\/\\:]/;
55 56 this.nbformat = 3 // Increment this when changing the nbformat
56 57 this.nbformat_minor = 0 // Increment this when changing the nbformat
@@ -74,7 +75,7 b' var IPython = (function (IPython) {'
74 75 * @method baseProjectUrl
75 76 * @return {String} The base project URL
76 77 */
77 Notebook.prototype.baseProjectUrl = function(){
78 Notebook.prototype.baseProjectUrl = function() {
78 79 return this._baseProjectUrl || $('body').data('baseProjectUrl');
79 80 };
80 81
@@ -92,12 +93,13 b' var IPython = (function (IPython) {'
92 93 * @method create_elements
93 94 */
94 95 Notebook.prototype.create_elements = function () {
96 var that = this;
97 this.element.attr('tabindex','-1');
98 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
95 99 // We add this end_space div to the end of the notebook div to:
96 100 // i) provide a margin between the last cell and the end of the notebook
97 101 // ii) to prevent the div from scrolling up when the last cell is being
98 102 // edited, but is too low on the page, which browsers will do automatically.
99 var that = this;
100 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
101 103 var end_space = $('<div/>').addClass('end_space');
102 104 end_space.dblclick(function (e) {
103 105 var ncells = that.ncells();
@@ -105,7 +107,6 b' var IPython = (function (IPython) {'
105 107 });
106 108 this.element.append(this.container);
107 109 this.container.append(end_space);
108 $('div#notebook').addClass('border-box-sizing');
109 110 };
110 111
111 112 /**
@@ -131,7 +132,17 b' var IPython = (function (IPython) {'
131 132 var index = that.find_cell_index(data.cell);
132 133 that.select(index);
133 134 });
134
135
136 $([IPython.events]).on('edit_mode.Cell', function (event, data) {
137 var index = that.find_cell_index(data.cell);
138 that.select(index);
139 that.edit_mode();
140 });
141
142 $([IPython.events]).on('command_mode.Cell', function (event, data) {
143 that.command_mode();
144 });
145
135 146 $([IPython.events]).on('status_autorestarting.Kernel', function () {
136 147 IPython.dialog.modal({
137 148 title: "Kernel Restarting",
@@ -144,220 +155,25 b' var IPython = (function (IPython) {'
144 155 });
145 156 });
146 157
147
148 $(document).keydown(function (event) {
149
150 // Save (CTRL+S) or (AppleKey+S)
151 //metaKey = applekey on mac
152 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
153 that.save_checkpoint();
154 event.preventDefault();
155 return false;
156 } else if (event.which === key.ESC) {
157 // Intercept escape at highest level to avoid closing
158 // websocket connection with firefox
159 IPython.pager.collapse();
160 event.preventDefault();
161 } else if (event.which === key.SHIFT) {
162 // ignore shift keydown
163 return true;
164 }
165 if (event.which === key.UPARROW && !event.shiftKey) {
166 var cell = that.get_selected_cell();
167 if (cell && cell.at_top()) {
168 event.preventDefault();
169 that.select_prev();
170 };
171 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
172 var cell = that.get_selected_cell();
173 if (cell && cell.at_bottom()) {
174 event.preventDefault();
175 that.select_next();
176 };
177 } else if (event.which === key.ENTER && event.shiftKey) {
178 that.execute_selected_cell();
179 return false;
180 } else if (event.which === key.ENTER && event.altKey) {
181 // Execute code cell, and insert new in place
182 that.execute_selected_cell();
183 // Only insert a new cell, if we ended up in an already populated cell
184 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
185 that.insert_cell_above('code');
186 }
187 return false;
188 } else if (event.which === key.ENTER && event.ctrlKey) {
189 that.execute_selected_cell({terminal:true});
190 return false;
191 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
192 that.control_key_active = true;
193 return false;
194 } else if (event.which === 88 && that.control_key_active) {
195 // Cut selected cell = x
196 that.cut_cell();
197 that.control_key_active = false;
198 return false;
199 } else if (event.which === 67 && that.control_key_active) {
200 // Copy selected cell = c
201 that.copy_cell();
202 that.control_key_active = false;
203 return false;
204 } else if (event.which === 86 && that.control_key_active) {
205 // Paste below selected cell = v
206 that.paste_cell_below();
207 that.control_key_active = false;
208 return false;
209 } else if (event.which === 68 && that.control_key_active) {
210 // Delete selected cell = d
211 that.delete_cell();
212 that.control_key_active = false;
213 return false;
214 } else if (event.which === 65 && that.control_key_active) {
215 // Insert code cell above selected = a
216 that.insert_cell_above('code');
217 that.control_key_active = false;
218 return false;
219 } else if (event.which === 66 && that.control_key_active) {
220 // Insert code cell below selected = b
221 that.insert_cell_below('code');
222 that.control_key_active = false;
223 return false;
224 } else if (event.which === 89 && that.control_key_active) {
225 // To code = y
226 that.to_code();
227 that.control_key_active = false;
228 return false;
229 } else if (event.which === 77 && that.control_key_active) {
230 // To markdown = m
231 that.to_markdown();
232 that.control_key_active = false;
233 return false;
234 } else if (event.which === 84 && that.control_key_active) {
235 // To Raw = t
236 that.to_raw();
237 that.control_key_active = false;
238 return false;
239 } else if (event.which === 49 && that.control_key_active) {
240 // To Heading 1 = 1
241 that.to_heading(undefined, 1);
242 that.control_key_active = false;
243 return false;
244 } else if (event.which === 50 && that.control_key_active) {
245 // To Heading 2 = 2
246 that.to_heading(undefined, 2);
247 that.control_key_active = false;
248 return false;
249 } else if (event.which === 51 && that.control_key_active) {
250 // To Heading 3 = 3
251 that.to_heading(undefined, 3);
252 that.control_key_active = false;
253 return false;
254 } else if (event.which === 52 && that.control_key_active) {
255 // To Heading 4 = 4
256 that.to_heading(undefined, 4);
257 that.control_key_active = false;
258 return false;
259 } else if (event.which === 53 && that.control_key_active) {
260 // To Heading 5 = 5
261 that.to_heading(undefined, 5);
262 that.control_key_active = false;
263 return false;
264 } else if (event.which === 54 && that.control_key_active) {
265 // To Heading 6 = 6
266 that.to_heading(undefined, 6);
267 that.control_key_active = false;
268 return false;
269 } else if (event.which === 79 && that.control_key_active) {
270 // Toggle output = o
271 if (event.shiftKey){
272 that.toggle_output_scroll();
273 } else {
274 that.toggle_output();
275 }
276 that.control_key_active = false;
277 return false;
278 } else if (event.which === 83 && that.control_key_active) {
279 // Save notebook = s
280 that.save_checkpoint();
281 that.control_key_active = false;
282 return false;
283 } else if (event.which === 74 && that.control_key_active) {
284 // Move cell down = j
285 that.move_cell_down();
286 that.control_key_active = false;
287 return false;
288 } else if (event.which === 75 && that.control_key_active) {
289 // Move cell up = k
290 that.move_cell_up();
291 that.control_key_active = false;
292 return false;
293 } else if (event.which === 80 && that.control_key_active) {
294 // Select previous = p
295 that.select_prev();
296 that.control_key_active = false;
297 return false;
298 } else if (event.which === 78 && that.control_key_active) {
299 // Select next = n
300 that.select_next();
301 that.control_key_active = false;
302 return false;
303 } else if (event.which === 76 && that.control_key_active) {
304 // Toggle line numbers = l
305 that.cell_toggle_line_numbers();
306 that.control_key_active = false;
307 return false;
308 } else if (event.which === 73 && that.control_key_active) {
309 // Interrupt kernel = i
310 that.session.interrupt_kernel();
311 that.control_key_active = false;
312 return false;
313 } else if (event.which === 190 && that.control_key_active) {
314 // Restart kernel = . # matches qt console
315 that.restart_kernel();
316 that.control_key_active = false;
317 return false;
318 } else if (event.which === 72 && that.control_key_active) {
319 // Show keyboard shortcuts = h
320 IPython.quick_help.show_keyboard_shortcuts();
321 that.control_key_active = false;
322 return false;
323 } else if (event.which === 90 && that.control_key_active) {
324 // Undo last cell delete = z
325 that.undelete();
326 that.control_key_active = false;
327 return false;
328 } else if ((event.which === 189 || event.which === 173) &&
329 that.control_key_active) {
330 // how fun! '-' is 189 in Chrome, but 173 in FF and Opera
331 // Split cell = -
332 that.split_cell();
333 that.control_key_active = false;
334 return false;
335 } else if (that.control_key_active) {
336 that.control_key_active = false;
337 return true;
338 }
339 return true;
340 });
341
342 var collapse_time = function(time){
158 var collapse_time = function (time) {
343 159 var app_height = $('#ipython-main-app').height(); // content height
344 160 var splitter_height = $('div#pager_splitter').outerHeight(true);
345 161 var new_height = app_height - splitter_height;
346 162 that.element.animate({height : new_height + 'px'}, time);
347 }
163 };
348 164
349 this.element.bind('collapse_pager', function (event,extrap) {
165 this.element.bind('collapse_pager', function (event, extrap) {
350 166 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
351 167 collapse_time(time);
352 168 });
353 169
354 var expand_time = function(time) {
170 var expand_time = function (time) {
355 171 var app_height = $('#ipython-main-app').height(); // content height
356 172 var splitter_height = $('div#pager_splitter').outerHeight(true);
357 173 var pager_height = $('div#pager').outerHeight(true);
358 174 var new_height = app_height - pager_height - splitter_height;
359 175 that.element.animate({height : new_height + 'px'}, time);
360 }
176 };
361 177
362 178 this.element.bind('expand_pager', function (event, extrap) {
363 179 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
@@ -650,6 +466,7 b' var IPython = (function (IPython) {'
650 466 if (this.is_valid_cell_index(index)) {
651 467 var sindex = this.get_selected_index()
652 468 if (sindex !== null && index !== sindex) {
469 this.command_mode();
653 470 this.get_cell(sindex).unselect();
654 471 };
655 472 var cell = this.get_cell(index);
@@ -692,6 +509,48 b' var IPython = (function (IPython) {'
692 509 };
693 510
694 511
512 // Edit/Command mode
513
514 Notebook.prototype.get_edit_index = function () {
515 var result = null;
516 this.get_cell_elements().filter(function (index) {
517 if ($(this).data("cell").mode === 'edit') {
518 result = index;
519 };
520 });
521 return result;
522 };
523
524 Notebook.prototype.command_mode = function () {
525 if (this.mode !== 'command') {
526 var index = this.get_edit_index();
527 var cell = this.get_cell(index);
528 if (cell) {
529 cell.command_mode();
530 };
531 this.mode = 'command';
532 IPython.keyboard_manager.command_mode();
533 };
534 };
535
536 Notebook.prototype.edit_mode = function () {
537 if (this.mode !== 'edit') {
538 var cell = this.get_selected_cell();
539 if (cell === null) {return;} // No cell is selected
540 // We need to set the mode to edit to prevent reentering this method
541 // when cell.edit_mode() is called below.
542 this.mode = 'edit';
543 IPython.keyboard_manager.edit_mode();
544 cell.edit_mode();
545 };
546 };
547
548 Notebook.prototype.focus_cell = function () {
549 var cell = this.get_selected_cell();
550 if (cell === null) {return;} // No cell is selected
551 cell.focus_cell();
552 };
553
695 554 // Cell movement
696 555
697 556 /**
@@ -710,6 +569,8 b' var IPython = (function (IPython) {'
710 569 tomove.detach();
711 570 pivot.before(tomove);
712 571 this.select(i-1);
572 var cell = this.get_selected_cell();
573 cell.focus_cell();
713 574 };
714 575 this.set_dirty(true);
715 576 };
@@ -726,13 +587,15 b' var IPython = (function (IPython) {'
726 587 **/
727 588 Notebook.prototype.move_cell_down = function (index) {
728 589 var i = this.index_or_selected(index);
729 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
590 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
730 591 var pivot = this.get_cell_element(i+1);
731 592 var tomove = this.get_cell_element(i);
732 593 if (pivot !== null && tomove !== null) {
733 594 tomove.detach();
734 595 pivot.after(tomove);
735 596 this.select(i+1);
597 var cell = this.get_selected_cell();
598 cell.focus_cell();
736 599 };
737 600 };
738 601 this.set_dirty();
@@ -755,9 +618,18 b' var IPython = (function (IPython) {'
755 618 this.undelete_backup = cell.toJSON();
756 619 $('#undelete_cell').removeClass('disabled');
757 620 if (this.is_valid_cell_index(i)) {
621 var old_ncells = this.ncells();
758 622 var ce = this.get_cell_element(i);
759 623 ce.remove();
760 if (i === (this.ncells())) {
624 if (i === 0) {
625 // Always make sure we have at least one cell.
626 if (old_ncells === 1) {
627 this.insert_cell_below('code');
628 }
629 this.select(0);
630 this.undelete_index = 0;
631 this.undelete_below = false;
632 } else if (i === old_ncells-1 && i !== 0) {
761 633 this.select(i-1);
762 634 this.undelete_index = i - 1;
763 635 this.undelete_below = true;
@@ -773,6 +645,42 b' var IPython = (function (IPython) {'
773 645 };
774 646
775 647 /**
648 * Restore the most recently deleted cell.
649 *
650 * @method undelete
651 */
652 Notebook.prototype.undelete_cell = function() {
653 if (this.undelete_backup !== null && this.undelete_index !== null) {
654 var current_index = this.get_selected_index();
655 if (this.undelete_index < current_index) {
656 current_index = current_index + 1;
657 }
658 if (this.undelete_index >= this.ncells()) {
659 this.select(this.ncells() - 1);
660 }
661 else {
662 this.select(this.undelete_index);
663 }
664 var cell_data = this.undelete_backup;
665 var new_cell = null;
666 if (this.undelete_below) {
667 new_cell = this.insert_cell_below(cell_data.cell_type);
668 } else {
669 new_cell = this.insert_cell_above(cell_data.cell_type);
670 }
671 new_cell.fromJSON(cell_data);
672 if (this.undelete_below) {
673 this.select(current_index+1);
674 } else {
675 this.select(current_index);
676 }
677 this.undelete_backup = null;
678 this.undelete_index = null;
679 }
680 $('#undelete_cell').addClass('disabled');
681 }
682
683 /**
776 684 * Insert a cell so that after insertion the cell is at given index.
777 685 *
778 686 * Similar to insert_above, but index parameter is mandatory
@@ -804,10 +712,14 b' var IPython = (function (IPython) {'
804 712 cell = new IPython.HeadingCell();
805 713 }
806 714
807 if(this._insert_element_at_index(cell.element,index)){
715 if(this._insert_element_at_index(cell.element,index)) {
808 716 cell.render();
809 this.select(this.find_cell_index(cell));
810 717 $([IPython.events]).trigger('create.Cell', {'cell': cell, 'index': index});
718 cell.refresh();
719 // We used to select the cell after we refresh it, but there
720 // are now cases were this method is called where select is
721 // not appropriate. The selection logic should be handled by the
722 // caller of the the top level insert_cell methods.
811 723 this.set_dirty(true);
812 724 }
813 725 }
@@ -923,6 +835,8 b' var IPython = (function (IPython) {'
923 835 // to this state, instead of a blank cell
924 836 target_cell.code_mirror.clearHistory();
925 837 source_element.remove();
838 this.select(i);
839 this.edit_mode();
926 840 this.set_dirty(true);
927 841 };
928 842 };
@@ -945,13 +859,15 b' var IPython = (function (IPython) {'
945 859 if (text === source_cell.placeholder) {
946 860 text = '';
947 861 };
948 // The edit must come before the set_text.
949 target_cell.edit();
862 // We must show the editor before setting its contents
863 target_cell.unrender();
950 864 target_cell.set_text(text);
951 865 // make this value the starting point, so that we can only undo
952 866 // to this state, instead of a blank cell
953 867 target_cell.code_mirror.clearHistory();
954 868 source_element.remove();
869 this.select(i);
870 this.edit_mode();
955 871 this.set_dirty(true);
956 872 };
957 873 };
@@ -975,13 +891,15 b' var IPython = (function (IPython) {'
975 891 if (text === source_cell.placeholder) {
976 892 text = '';
977 893 };
978 // The edit must come before the set_text.
979 target_cell.edit();
894 // We must show the editor before setting its contents
895 target_cell.unrender();
980 896 target_cell.set_text(text);
981 897 // make this value the starting point, so that we can only undo
982 898 // to this state, instead of a blank cell
983 899 target_cell.code_mirror.clearHistory();
984 900 source_element.remove();
901 this.select(i);
902 this.edit_mode();
985 903 this.set_dirty(true);
986 904 };
987 905 };
@@ -1009,16 +927,18 b' var IPython = (function (IPython) {'
1009 927 if (text === source_cell.placeholder) {
1010 928 text = '';
1011 929 };
1012 // The edit must come before the set_text.
930 // We must show the editor before setting its contents
1013 931 target_cell.set_level(level);
1014 target_cell.edit();
932 target_cell.unrender();
1015 933 target_cell.set_text(text);
1016 934 // make this value the starting point, so that we can only undo
1017 935 // to this state, instead of a blank cell
1018 936 target_cell.code_mirror.clearHistory();
1019 937 source_element.remove();
1020 this.set_dirty(true);
938 this.select(i);
1021 939 };
940 this.edit_mode();
941 this.set_dirty(true);
1022 942 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
1023 943 {'cell_type':'heading',level:level}
1024 944 );
@@ -1123,40 +1043,6 b' var IPython = (function (IPython) {'
1123 1043 };
1124 1044 };
1125 1045
1126 // Cell undelete
1127
1128 /**
1129 * Restore the most recently deleted cell.
1130 *
1131 * @method undelete
1132 */
1133 Notebook.prototype.undelete = function() {
1134 if (this.undelete_backup !== null && this.undelete_index !== null) {
1135 var current_index = this.get_selected_index();
1136 if (this.undelete_index < current_index) {
1137 current_index = current_index + 1;
1138 }
1139 if (this.undelete_index >= this.ncells()) {
1140 this.select(this.ncells() - 1);
1141 }
1142 else {
1143 this.select(this.undelete_index);
1144 }
1145 var cell_data = this.undelete_backup;
1146 var new_cell = null;
1147 if (this.undelete_below) {
1148 new_cell = this.insert_cell_below(cell_data.cell_type);
1149 } else {
1150 new_cell = this.insert_cell_above(cell_data.cell_type);
1151 }
1152 new_cell.fromJSON(cell_data);
1153 this.select(current_index);
1154 this.undelete_backup = null;
1155 this.undelete_index = null;
1156 }
1157 $('#undelete_cell').addClass('disabled');
1158 }
1159
1160 1046 // Split/merge
1161 1047
1162 1048 /**
@@ -1165,24 +1051,25 b' var IPython = (function (IPython) {'
1165 1051 * @method split_cell
1166 1052 */
1167 1053 Notebook.prototype.split_cell = function () {
1168 // Todo: implement spliting for other cell types.
1054 var mdc = IPython.MarkdownCell;
1055 var rc = IPython.RawCell;
1169 1056 var cell = this.get_selected_cell();
1170 1057 if (cell.is_splittable()) {
1171 1058 var texta = cell.get_pre_cursor();
1172 1059 var textb = cell.get_post_cursor();
1173 1060 if (cell instanceof IPython.CodeCell) {
1061 // In this case the operations keep the notebook in its existing mode
1062 // so we don't need to do any post-op mode changes.
1174 1063 cell.set_text(textb);
1175 1064 var new_cell = this.insert_cell_above('code');
1176 1065 new_cell.set_text(texta);
1177 this.select_next();
1178 } else if (cell instanceof IPython.MarkdownCell) {
1066 } else if ((cell instanceof mdc && !cell.rendered) || (cell instanceof rc)) {
1067 // We know cell is !rendered so we can use set_text.
1179 1068 cell.set_text(textb);
1180 cell.render();
1181 var new_cell = this.insert_cell_above('markdown');
1182 new_cell.edit(); // editor must be visible to call set_text
1069 var new_cell = this.insert_cell_above(cell.cell_type);
1070 // Unrender the new cell so we can call set_text.
1071 new_cell.unrender();
1183 1072 new_cell.set_text(texta);
1184 new_cell.render();
1185 this.select_next();
1186 1073 }
1187 1074 };
1188 1075 };
@@ -1193,8 +1080,11 b' var IPython = (function (IPython) {'
1193 1080 * @method merge_cell_above
1194 1081 */
1195 1082 Notebook.prototype.merge_cell_above = function () {
1083 var mdc = IPython.MarkdownCell;
1084 var rc = IPython.RawCell;
1196 1085 var index = this.get_selected_index();
1197 1086 var cell = this.get_cell(index);
1087 var render = cell.rendered;
1198 1088 if (!cell.is_mergeable()) {
1199 1089 return;
1200 1090 }
@@ -1207,10 +1097,14 b' var IPython = (function (IPython) {'
1207 1097 var text = cell.get_text();
1208 1098 if (cell instanceof IPython.CodeCell) {
1209 1099 cell.set_text(upper_text+'\n'+text);
1210 } else if (cell instanceof IPython.MarkdownCell) {
1211 cell.edit();
1212 cell.set_text(upper_text+'\n'+text);
1213 cell.render();
1100 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1101 cell.unrender(); // Must unrender before we set_text.
1102 cell.set_text(upper_text+'\n\n'+text);
1103 if (render) {
1104 // The rendered state of the final cell should match
1105 // that of the original selected cell;
1106 cell.render();
1107 }
1214 1108 };
1215 1109 this.delete_cell(index-1);
1216 1110 this.select(this.find_cell_index(cell));
@@ -1223,8 +1117,11 b' var IPython = (function (IPython) {'
1223 1117 * @method merge_cell_below
1224 1118 */
1225 1119 Notebook.prototype.merge_cell_below = function () {
1120 var mdc = IPython.MarkdownCell;
1121 var rc = IPython.RawCell;
1226 1122 var index = this.get_selected_index();
1227 1123 var cell = this.get_cell(index);
1124 var render = cell.rendered;
1228 1125 if (!cell.is_mergeable()) {
1229 1126 return;
1230 1127 }
@@ -1237,10 +1134,14 b' var IPython = (function (IPython) {'
1237 1134 var text = cell.get_text();
1238 1135 if (cell instanceof IPython.CodeCell) {
1239 1136 cell.set_text(text+'\n'+lower_text);
1240 } else if (cell instanceof IPython.MarkdownCell) {
1241 cell.edit();
1242 cell.set_text(text+'\n'+lower_text);
1243 cell.render();
1137 } else if ((cell instanceof mdc) || (cell instanceof rc)) {
1138 cell.unrender(); // Must unrender before we set_text.
1139 cell.set_text(text+'\n\n'+lower_text);
1140 if (render) {
1141 // The rendered state of the final cell should match
1142 // that of the original selected cell;
1143 cell.render();
1144 }
1244 1145 };
1245 1146 this.delete_cell(index+1);
1246 1147 this.select(this.find_cell_index(cell));
@@ -1433,35 +1334,76 b' var IPython = (function (IPython) {'
1433 1334 };
1434 1335
1435 1336 /**
1436 * Run the selected cell.
1337 * Execute or render cell outputs and go into command mode.
1437 1338 *
1438 * Execute or render cell outputs.
1339 * @method execute_cell
1340 */
1341 Notebook.prototype.execute_cell = function () {
1342 // mode = shift, ctrl, alt
1343 var cell = this.get_selected_cell();
1344 var cell_index = this.find_cell_index(cell);
1345
1346 cell.execute();
1347 this.command_mode();
1348 cell.focus_cell();
1349 this.set_dirty(true);
1350 }
1351
1352 /**
1353 * Execute or render cell outputs and insert a new cell below.
1439 1354 *
1440 * @method execute_selected_cell
1441 * @param {Object} options Customize post-execution behavior
1355 * @method execute_cell_and_insert_below
1442 1356 */
1443 Notebook.prototype.execute_selected_cell = function (options) {
1444 // add_new: should a new cell be added if we are at the end of the nb
1445 // terminal: execute in terminal mode, which stays in the current cell
1446 var default_options = {terminal: false, add_new: true};
1447 $.extend(default_options, options);
1448 var that = this;
1449 var cell = that.get_selected_cell();
1450 var cell_index = that.find_cell_index(cell);
1451 if (cell instanceof IPython.CodeCell) {
1452 cell.execute();
1357 Notebook.prototype.execute_cell_and_insert_below = function () {
1358 var cell = this.get_selected_cell();
1359 var cell_index = this.find_cell_index(cell);
1360
1361 cell.execute();
1362
1363 // If we are at the end always insert a new cell and return
1364 if (cell_index === (this.ncells()-1)) {
1365 this.insert_cell_below('code');
1366 this.select(cell_index+1);
1367 this.edit_mode();
1368 this.scroll_to_bottom();
1369 this.set_dirty(true);
1370 return;
1453 1371 }
1454 if (default_options.terminal) {
1455 cell.select_all();
1456 } else {
1457 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1458 that.insert_cell_below('code');
1459 // If we are adding a new cell at the end, scroll down to show it.
1460 that.scroll_to_bottom();
1461 } else {
1462 that.select(cell_index+1);
1463 };
1464 };
1372
1373 // Only insert a new cell, if we ended up in an already populated cell
1374 var next_text = this.get_cell(cell_index+1).get_text();
1375 if (/\S/.test(next_text) === true) {
1376 this.insert_cell_below('code');
1377 }
1378 this.select(cell_index+1);
1379 this.edit_mode();
1380 this.set_dirty(true);
1381 };
1382
1383 /**
1384 * Execute or render cell outputs and select the next cell.
1385 *
1386 * @method execute_cell_and_select_below
1387 */
1388 Notebook.prototype.execute_cell_and_select_below = function () {
1389
1390 var cell = this.get_selected_cell();
1391 var cell_index = this.find_cell_index(cell);
1392
1393 cell.execute();
1394
1395 // If we are at the end always insert a new cell and return
1396 if (cell_index === (this.ncells()-1)) {
1397 this.insert_cell_below('code');
1398 this.select(cell_index+1);
1399 this.edit_mode();
1400 this.scroll_to_bottom();
1401 this.set_dirty(true);
1402 return;
1403 }
1404
1405 this.select(cell_index+1);
1406 this.get_cell(cell_index+1).focus_cell();
1465 1407 this.set_dirty(true);
1466 1408 };
1467 1409
@@ -1504,7 +1446,7 b' var IPython = (function (IPython) {'
1504 1446 Notebook.prototype.execute_cell_range = function (start, end) {
1505 1447 for (var i=start; i<end; i++) {
1506 1448 this.select(i);
1507 this.execute_selected_cell({add_new:false});
1449 this.execute_cell();
1508 1450 };
1509 1451 };
1510 1452
@@ -1584,7 +1526,7 b' var IPython = (function (IPython) {'
1584 1526 cell_data.cell_type = 'raw';
1585 1527 }
1586 1528
1587 new_cell = this.insert_cell_at_bottom(cell_data.cell_type);
1529 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1588 1530 new_cell.fromJSON(cell_data);
1589 1531 };
1590 1532 };
@@ -1909,9 +1851,13 b' var IPython = (function (IPython) {'
1909 1851 this.fromJSON(data);
1910 1852 if (this.ncells() === 0) {
1911 1853 this.insert_cell_below('code');
1854 this.select(0);
1855 this.edit_mode();
1856 } else {
1857 this.select(0);
1858 this.command_mode();
1912 1859 };
1913 1860 this.set_dirty(false);
1914 this.select(0);
1915 1861 this.scroll_to_top();
1916 1862 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1917 1863 var msg = "This notebook has been converted from an older " +
@@ -509,6 +509,7 b' var IPython = (function (IPython) {'
509 509
510 510 OutputArea.prototype.append_html = function (html, md, element) {
511 511 var toinsert = this.create_output_subarea(md, "output_html rendered_html");
512 IPython.keyboard_manager.register_events(toinsert);
512 513 toinsert.append(html);
513 514 element.append(toinsert);
514 515 };
@@ -517,6 +518,7 b' var IPython = (function (IPython) {'
517 518 OutputArea.prototype.append_javascript = function (js, md, container) {
518 519 // We just eval the JS code, element appears in the local scope.
519 520 var element = this.create_output_subarea(md, "output_javascript");
521 IPython.keyboard_manager.register_events(element);
520 522 container.append(element);
521 523 try {
522 524 eval(js);
@@ -646,11 +648,18 b' var IPython = (function (IPython) {'
646 648 })
647 649 )
648 650 );
651
649 652 this.element.append(area);
650 // weirdly need double-focus now,
651 // otherwise only the cell will be focused
652 area.find("input.raw_input").focus().focus();
653 var raw_input = area.find('input.raw_input');
654 // Register events that enable/disable the keyboard manager while raw
655 // input is focused.
656 IPython.keyboard_manager.register_events(raw_input);
657 // Note, the following line used to read raw_input.focus().focus().
658 // This seemed to be needed otherwise only the cell would be focused.
659 // But with the modal UI, this seems to work fine with one call to focus().
660 raw_input.focus();
653 661 }
662
654 663 OutputArea.prototype._submit_raw_input = function (evt) {
655 664 var container = this.element.find("div.raw_input");
656 665 var theprompt = container.find("span.input_prompt");
@@ -23,44 +23,35 b' var IPython = (function (IPython) {'
23 23 $(this.shortcut_dialog).modal("toggle");
24 24 return;
25 25 }
26 var body = $('<div/>');
27 var shortcuts = [
28 {key: 'Shift-Enter', help: 'run cell'},
29 {key: 'Ctrl-Enter', help: 'run cell in-place'},
30 {key: 'Alt-Enter', help: 'run cell, insert below'},
31 {key: 'Ctrl-m x', help: 'cut cell'},
32 {key: 'Ctrl-m c', help: 'copy cell'},
33 {key: 'Ctrl-m v', help: 'paste cell'},
34 {key: 'Ctrl-m d', help: 'delete cell'},
35 {key: 'Ctrl-m z', help: 'undo last cell deletion'},
36 {key: 'Ctrl-m -', help: 'split cell'},
37 {key: 'Ctrl-m a', help: 'insert cell above'},
38 {key: 'Ctrl-m b', help: 'insert cell below'},
39 {key: 'Ctrl-m o', help: 'toggle output'},
40 {key: 'Ctrl-m O', help: 'toggle output scroll'},
41 {key: 'Ctrl-m l', help: 'toggle line numbers'},
42 {key: 'Ctrl-m s', help: 'save notebook'},
43 {key: 'Ctrl-m j', help: 'move cell down'},
44 {key: 'Ctrl-m k', help: 'move cell up'},
45 {key: 'Ctrl-m y', help: 'code cell'},
46 {key: 'Ctrl-m m', help: 'markdown cell'},
47 {key: 'Ctrl-m t', help: 'raw cell'},
48 {key: 'Ctrl-m 1-6', help: 'heading 1-6 cell'},
49 {key: 'Ctrl-m p', help: 'select previous'},
50 {key: 'Ctrl-m n', help: 'select next'},
51 {key: 'Ctrl-m i', help: 'interrupt kernel'},
52 {key: 'Ctrl-m .', help: 'restart kernel'},
53 {key: 'Ctrl-m h', help: 'show keyboard shortcuts'}
54 ];
55 for (var i=0; i<shortcuts.length; i++) {
56 body.append($('<div>').addClass('quickhelp').
57 append($('<span/>').addClass('shortcut_key').html(shortcuts[i].key)).
58 append($('<span/>').addClass('shortcut_descr').html(' : ' + shortcuts[i].help))
59 );
60 };
26 var command_shortcuts = IPython.keyboard_manager.command_shortcuts.help();
27 var edit_shortcuts = IPython.keyboard_manager.edit_shortcuts.help();
28 var help, shortcut;
29 var i, half, n;
30 var element = $('<div/>');
31
32 // The documentation
33 var doc = $('<div/>').addClass('alert');
34 doc.append(
35 $('<button/>').addClass('close').attr('data-dismiss','alert').html('&times')
36 ).append(
37 'The IPython Notebook has two different keyboard input modes. <b>Edit mode</b> '+
38 'allow you the type code/text into a cell and is indicated by a green cell '+
39 'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
40 'and is indicated by a grey cell border.'
41 )
42 element.append(doc);
43
44 // Command mode
45 var cmd_div = this.build_command_help();
46 element.append(cmd_div);
47
48 // Edit mode
49 var edit_div = this.build_edit_help();
50 element.append(edit_div);
51
61 52 this.shortcut_dialog = IPython.dialog.modal({
62 53 title : "Keyboard shortcuts",
63 body : body,
54 body : element,
64 55 destroy : false,
65 56 buttons : {
66 57 Close : {}
@@ -68,6 +59,72 b' var IPython = (function (IPython) {'
68 59 });
69 60 };
70 61
62 QuickHelp.prototype.build_command_help = function () {
63 var command_shortcuts = IPython.keyboard_manager.command_shortcuts.help();
64 var help, shortcut;
65 var i, half, n;
66
67 // Command mode
68 var cmd_div = $('<div/>').append($('<h4>Command Mode (press ESC to enable)</h4>'));
69 var cmd_sub_div = $('<div/>').addClass('hbox');
70 var cmd_col1 = $('<div/>').addClass('box-flex0');
71 var cmd_col2 = $('<div/>').addClass('box-flex0');
72 n = command_shortcuts.length;
73 half = ~~(n/2); // Truncate :)
74 for (i=0; i<half; i++) {
75 help = command_shortcuts[i]['help'];
76 shortcut = command_shortcuts[i]['shortcut'];
77 cmd_col1.append($('<div>').addClass('quickhelp').
78 append($('<span/>').addClass('shortcut_key').html(shortcut)).
79 append($('<span/>').addClass('shortcut_descr').html(' : ' + help))
80 );
81 };
82 for (i=half; i<n; i++) {
83 help = command_shortcuts[i]['help'];
84 shortcut = command_shortcuts[i]['shortcut'];
85 cmd_col2.append($('<div>').addClass('quickhelp').
86 append($('<span/>').addClass('shortcut_key').html(shortcut)).
87 append($('<span/>').addClass('shortcut_descr').html(' : ' + help))
88 );
89 };
90 cmd_sub_div.append(cmd_col1).append(cmd_col2);
91 cmd_div.append(cmd_sub_div);
92 return cmd_div;
93 }
94
95 QuickHelp.prototype.build_edit_help = function () {
96 var edit_shortcuts = IPython.keyboard_manager.edit_shortcuts.help();
97 var help, shortcut;
98 var i, half, n;
99
100 // Edit mode
101 var edit_div = $('<div/>').append($('<h4>Edit Mode (press ENTER to enable)</h4>'));
102 var edit_sub_div = $('<div/>').addClass('hbox');
103 var edit_col1 = $('<div/>').addClass('box-flex0');
104 var edit_col2 = $('<div/>').addClass('box-flex0');
105 n = edit_shortcuts.length;
106 half = ~~(n/2); // Truncate :)
107 for (i=0; i<half; i++) {
108 help = edit_shortcuts[i]['help'];
109 shortcut = edit_shortcuts[i]['shortcut'];
110 edit_col1.append($('<div>').addClass('quickhelp').
111 append($('<span/>').addClass('shortcut_key').html(shortcut)).
112 append($('<span/>').addClass('shortcut_descr').html(' : ' + help))
113 );
114 };
115 for (i=half; i<n; i++) {
116 help = edit_shortcuts[i]['help'];
117 shortcut = edit_shortcuts[i]['shortcut'];
118 edit_col2.append($('<div>').addClass('quickhelp').
119 append($('<span/>').addClass('shortcut_key').html(shortcut)).
120 append($('<span/>').addClass('shortcut_descr').html(' : ' + help))
121 );
122 };
123 edit_sub_div.append(edit_col1).append(edit_col2);
124 edit_div.append(edit_sub_div);
125 return edit_div;
126 }
127
71 128 // Set module variables
72 129 IPython.QuickHelp = QuickHelp;
73 130
@@ -41,7 +41,7 b' var IPython = (function (IPython) {'
41 41
42 42 // we cannot put this as a class key as it has handle to "this".
43 43 var cm_overwrite_options = {
44 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
44 onKeyEvent: $.proxy(this.handle_keyevent,this)
45 45 };
46 46
47 47 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
@@ -64,7 +64,6 b' var IPython = (function (IPython) {'
64 64 };
65 65
66 66
67
68 67 /**
69 68 * Create the DOM element of the TextCell
70 69 * @method create_element
@@ -101,19 +100,26 b' var IPython = (function (IPython) {'
101 100 TextCell.prototype.bind_events = function () {
102 101 IPython.Cell.prototype.bind_events.apply(this);
103 102 var that = this;
104 this.element.keydown(function (event) {
105 if (event.which === 13 && !event.shiftKey) {
106 if (that.rendered) {
107 that.edit();
108 return false;
109 };
110 };
111 });
103
112 104 this.element.dblclick(function () {
113 that.edit();
105 if (that.selected === false) {
106 $([IPython.events]).trigger('select.Cell', {'cell':that});
107 };
108 $([IPython.events]).trigger('edit_mode.Cell', {cell: that});
114 109 });
115 110 };
116 111
112 TextCell.prototype.handle_keyevent = function (editor, event) {
113
114 // console.log('CM', this.mode, event.which, event.type)
115
116 if (this.mode === 'command') {
117 return true;
118 } else if (this.mode === 'edit') {
119 return this.handle_codemirror_keyevent(editor, event);
120 }
121 };
122
117 123 /**
118 124 * This method gets called in CodeMirror's onKeyDown/onKeyPress
119 125 * handlers and is used to provide custom key handling.
@@ -126,65 +132,86 b' var IPython = (function (IPython) {'
126 132 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
127 133 */
128 134 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
135 var that = this;
129 136
130 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
137 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey || event.altKey)) {
131 138 // Always ignore shift-enter in CodeMirror as we handle it.
132 139 return true;
140 } else if (event.which === key.UPARROW && event.type === 'keydown') {
141 // If we are not at the top, let CM handle the up arrow and
142 // prevent the global keydown handler from handling it.
143 if (!that.at_top()) {
144 event.stop();
145 return false;
146 } else {
147 return true;
148 };
149 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
150 // If we are not at the bottom, let CM handle the down arrow and
151 // prevent the global keydown handler from handling it.
152 if (!that.at_bottom()) {
153 event.stop();
154 return false;
155 } else {
156 return true;
157 };
158 } else if (event.which === key.ESC && event.type === 'keydown') {
159 if (that.code_mirror.options.keyMap === "vim-insert") {
160 // vim keyMap is active and in insert mode. In this case we leave vim
161 // insert mode, but remain in notebook edit mode.
162 // Let' CM handle this event and prevent global handling.
163 event.stop();
164 return false;
165 } else {
166 // vim keyMap is not active. Leave notebook edit mode.
167 // Don't let CM handle the event, defer to global handling.
168 return true;
169 }
133 170 }
134 171 return false;
135 172 };
136 173
137 /**
138 * Select the current cell and trigger 'focus'
139 * @method select
140 */
174 // Cell level actions
175
141 176 TextCell.prototype.select = function () {
142 IPython.Cell.prototype.select.apply(this);
143 var output = this.element.find("div.text_cell_render");
144 output.trigger('focus');
145 };
146
147 /**
148 * unselect the current cell and `render` it
149 * @method unselect
150 */
151 TextCell.prototype.unselect = function() {
152 // render on selection of another cell
153 this.render();
154 IPython.Cell.prototype.unselect.apply(this);
177 var cont = IPython.Cell.prototype.select.apply(this);
178 if (cont) {
179 if (this.mode === 'edit') {
180 this.code_mirror.refresh();
181 }
182 };
183 return cont;
155 184 };
156 185
157 /**
158 *
159 * put the current cell in edition mode
160 * @method edit
161 */
162 TextCell.prototype.edit = function () {
163 if (this.rendered === true) {
186 TextCell.prototype.unrender = function () {
187 if (this.read_only) return;
188 var cont = IPython.Cell.prototype.unrender.apply(this);
189 if (cont) {
164 190 var text_cell = this.element;
165 191 var output = text_cell.find("div.text_cell_render");
166 192 output.hide();
167 193 text_cell.find('div.text_cell_input').show();
168 this.code_mirror.refresh();
169 this.code_mirror.focus();
170 // We used to need an additional refresh() after the focus, but
171 // it appears that this has been fixed in CM. This bug would show
172 // up on FF when a newly loaded markdown cell was edited.
173 this.rendered = false;
174 194 if (this.get_text() === this.placeholder) {
175 195 this.set_text('');
176 196 this.refresh();
177 197 }
178 }
179 };
180 198
199 };
200 return cont;
201 };
181 202
182 /**
183 * Empty, Subclasses must define render.
184 * @method render
185 */
186 TextCell.prototype.render = function () {};
203 TextCell.prototype.execute = function () {
204 this.render();
205 };
187 206
207 TextCell.prototype.edit_mode = function () {
208 var cont = IPython.Cell.prototype.edit_mode.apply(this);
209 if (cont) {
210 this.unrender();
211 this.focus_editor();
212 };
213 return cont;
214 }
188 215
189 216 /**
190 217 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
@@ -222,36 +249,37 b' var IPython = (function (IPython) {'
222 249 };
223 250
224 251 /**
225 * not deprecated, but implementation wrong
226 252 * @method at_top
227 * @deprecated
228 * @return {Boolean} true is cell rendered, false otherwise
229 * I doubt this is what it is supposed to do
230 * this implementation is completly false
253 * @return {Boolean}
231 254 */
232 255 TextCell.prototype.at_top = function () {
233 256 if (this.rendered) {
234 257 return true;
235 258 } else {
236 return false;
237 }
259 var cursor = this.code_mirror.getCursor();
260 if (cursor.line === 0 && cursor.ch === 0) {
261 return true;
262 } else {
263 return false;
264 };
265 };
238 266 };
239 267
240
241 268 /**
242 * not deprecated, but implementation wrong
243 269 * @method at_bottom
244 * @deprecated
245 * @return {Boolean} true is cell rendered, false otherwise
246 * I doubt this is what it is supposed to do
247 * this implementation is completly false
270 * @return {Boolean}
248 271 * */
249 272 TextCell.prototype.at_bottom = function () {
250 273 if (this.rendered) {
251 274 return true;
252 275 } else {
253 return false;
254 }
276 var cursor = this.code_mirror.getCursor();
277 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
278 return true;
279 } else {
280 return false;
281 };
282 };
255 283 };
256 284
257 285 /**
@@ -306,16 +334,14 b' var IPython = (function (IPython) {'
306 334 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
307 335 }
308 336
309
310
311
312 337 MarkdownCell.prototype = new TextCell();
313 338
314 339 /**
315 340 * @method render
316 341 */
317 342 MarkdownCell.prototype.render = function () {
318 if (this.rendered === false) {
343 var cont = IPython.TextCell.prototype.render.apply(this);
344 if (cont) {
319 345 var text = this.get_text();
320 346 var math = null;
321 347 if (text === "") { text = this.placeholder; }
@@ -337,9 +363,9 b' var IPython = (function (IPython) {'
337 363 }
338 364 this.element.find('div.text_cell_input').hide();
339 365 this.element.find("div.text_cell_render").show();
340 this.typeset();
341 this.rendered = true;
342 }
366 this.typeset()
367 };
368 return cont;
343 369 };
344 370
345 371
@@ -351,15 +377,12 b' var IPython = (function (IPython) {'
351 377 * @extends IPython.TextCell
352 378 */
353 379 var RawCell = function (options) {
354 options = this.mergeopt(RawCell, options);
355
356 this.cell_type = 'raw';
357 TextCell.apply(this, [options]);
358 380
359 var that = this;
360 this.element.focusout(
361 function() { that.auto_highlight(); }
362 );
381 options = this.mergeopt(RawCell,options)
382 TextCell.apply(this, [options]);
383 this.cell_type = 'raw';
384 // RawCell should always hide its rendered div
385 this.element.find('div.text_cell_render').hide();
363 386 };
364 387
365 388 RawCell.options_default = {
@@ -368,10 +391,17 b' var IPython = (function (IPython) {'
368 391 "When passing through nbconvert, a Raw Cell's content is added to the output unmodified."
369 392 };
370 393
371
372
373 394 RawCell.prototype = new TextCell();
374 395
396 /** @method bind_events **/
397 RawCell.prototype.bind_events = function () {
398 TextCell.prototype.bind_events.apply(this);
399 var that = this
400 this.element.focusout(function() {
401 that.auto_highlight();
402 });
403 };
404
375 405 /**
376 406 * Trigger autodetection of highlight scheme for current cell
377 407 * @method auto_highlight
@@ -382,68 +412,16 b' var IPython = (function (IPython) {'
382 412
383 413 /** @method render **/
384 414 RawCell.prototype.render = function () {
385 this.rendered = true;
415 // Make sure that this cell type can never be rendered
416 if (this.rendered) {
417 this.unrender();
418 }
386 419 var text = this.get_text();
387 420 if (text === "") { text = this.placeholder; }
388 console.log('rendering', text);
389 421 this.set_text(text);
390 422 };
391 423
392 424
393 /** @method handle_codemirror_keyevent **/
394 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
395
396 var that = this;
397 if (event.which === key.UPARROW && event.type === 'keydown') {
398 // If we are not at the top, let CM handle the up arrow and
399 // prevent the global keydown handler from handling it.
400 if (!that.at_top()) {
401 event.stop();
402 return false;
403 } else {
404 return true;
405 };
406 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
407 // If we are not at the bottom, let CM handle the down arrow and
408 // prevent the global keydown handler from handling it.
409 if (!that.at_bottom()) {
410 event.stop();
411 return false;
412 } else {
413 return true;
414 };
415 };
416 return false;
417 };
418
419 /** @method select **/
420 RawCell.prototype.select = function () {
421 IPython.Cell.prototype.select.apply(this);
422 this.edit();
423 };
424
425 /** @method at_top **/
426 RawCell.prototype.at_top = function () {
427 var cursor = this.code_mirror.getCursor();
428 if (cursor.line === 0 && cursor.ch === 0) {
429 return true;
430 } else {
431 return false;
432 }
433 };
434
435
436 /** @method at_bottom **/
437 RawCell.prototype.at_bottom = function () {
438 var cursor = this.code_mirror.getCursor();
439 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
440 return true;
441 } else {
442 return false;
443 }
444 };
445
446
447 425 /**
448 426 * @class HeadingCell
449 427 * @extends IPython.TextCell
@@ -538,7 +516,8 b' var IPython = (function (IPython) {'
538 516
539 517
540 518 HeadingCell.prototype.render = function () {
541 if (this.rendered === false) {
519 var cont = IPython.TextCell.prototype.render.apply(this);
520 if (cont) {
542 521 var text = this.get_text();
543 522 var math = null;
544 523 // Markdown headings must be a single line
@@ -564,8 +543,9 b' var IPython = (function (IPython) {'
564 543 this.typeset();
565 544 this.element.find('div.text_cell_input').hide();
566 545 this.element.find("div.text_cell_render").show();
567 this.rendered = true;
546
568 547 };
548 return cont;
569 549 };
570 550
571 551 IPython.TextCell = TextCell;
@@ -6,6 +6,14 b' div.cell {'
6 6 .corner-all;
7 7 border : thin @border_color solid;
8 8 }
9
10 &.edit_mode {
11 .corner-all;
12 border : thin green solid;
13 }
14 }
15
16 div.cell {
9 17 width: 100%;
10 18 padding: 5px 5px 5px 0px;
11 19 /* This acts as a spacer between cells, that is outside the border */
@@ -28,6 +28,8 b' div#notebook {'
28 28 padding: 5px 5px 15px 5px;
29 29 margin: 0px;
30 30 border-top: 1px solid @border_color;
31 outline: none;
32 .border-box-sizing();
31 33 }
32 34
33 35 div.ui-widget-content {
@@ -9,7 +9,3 b''
9 9 display: inline-block;
10 10 }
11 11
12 div.quickhelp {
13 float: left;
14 width: 50%;
15 }
@@ -58,7 +58,9 b' input.engine_num_input{height:20px;margin-bottom:2px;padding-top:0;padding-botto'
58 58 .ansibgpurple{background-color:magenta;}
59 59 .ansibgcyan{background-color:cyan;}
60 60 .ansibggray{background-color:gray;}
61 div.cell{border:1px solid transparent;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;padding:5px 5px 5px 0px;margin:0px;outline:none;}div.cell.selected{border-radius:4px;border:thin #ababab solid;}
61 div.cell{border:1px solid transparent;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;}div.cell.selected{border-radius:4px;border:thin #ababab solid;}
62 div.cell.edit_mode{border-radius:4px;border:thin green solid;}
63 div.cell{width:100%;padding:5px 5px 5px 0px;margin:0px;outline:none;}
62 64 div.prompt{min-width:11ex;padding:0.4em;margin:0px;font-family:monospace;text-align:right;line-height:1.231em;}
63 65 div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;}
64 66 div.prompt:empty{padding-top:0;padding-bottom:0;}
@@ -1439,7 +1439,9 b' input.engine_num_input{height:20px;margin-bottom:2px;padding-top:0;padding-botto'
1439 1439 .ansibgpurple{background-color:magenta;}
1440 1440 .ansibgcyan{background-color:cyan;}
1441 1441 .ansibggray{background-color:gray;}
1442 div.cell{border:1px solid transparent;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;padding:5px 5px 5px 0px;margin:0px;outline:none;}div.cell.selected{border-radius:4px;border:thin #ababab solid;}
1442 div.cell{border:1px solid transparent;display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;}div.cell.selected{border-radius:4px;border:thin #ababab solid;}
1443 div.cell.edit_mode{border-radius:4px;border:thin green solid;}
1444 div.cell{width:100%;padding:5px 5px 5px 0px;margin:0px;outline:none;}
1443 1445 div.prompt{min-width:11ex;padding:0.4em;margin:0px;font-family:monospace;text-align:right;line-height:1.231em;}
1444 1446 div.inner_cell{display:-webkit-box;-webkit-box-orient:vertical;-webkit-box-align:stretch;display:-moz-box;-moz-box-orient:vertical;-moz-box-align:stretch;display:box;box-orient:vertical;box-align:stretch;width:100%;-webkit-box-flex:1;-moz-box-flex:1;box-flex:1;}
1445 1447 div.prompt:empty{padding-top:0;padding-bottom:0;}
@@ -1535,7 +1537,7 b' body{background-color:#ffffff;}'
1535 1537 body.notebook_app{overflow:hidden;}
1536 1538 span#notebook_name{height:1em;line-height:1em;padding:3px;border:none;font-size:146.5%;}
1537 1539 div#notebook_panel{margin:0px 0px 0px 0px;padding:0px;-webkit-box-shadow:0 -1px 10px rgba(0, 0, 0, 0.1);-moz-box-shadow:0 -1px 10px rgba(0, 0, 0, 0.1);box-shadow:0 -1px 10px rgba(0, 0, 0, 0.1);}
1538 div#notebook{overflow-y:scroll;overflow-x:auto;width:100%;padding:5px 5px 15px 5px;margin:0px;border-top:1px solid #ababab;}
1540 div#notebook{overflow-y:scroll;overflow-x:auto;width:100%;padding:5px 5px 15px 5px;margin:0px;border-top:1px solid #ababab;outline:none;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;}
1539 1541 div.ui-widget-content{border:1px solid #ababab;outline:none;}
1540 1542 pre.dialog{background-color:#f7f7f7;border:1px solid #ddd;border-radius:4px;padding:0.4em;padding-left:2em;}
1541 1543 p.dialog{padding:0.2em;}
@@ -1567,7 +1569,6 b' div#pager_splitter{height:8px;}'
1567 1569 div#pager{overflow:auto;display:none;}div#pager pre{font-size:13px;line-height:1.231em;color:#000000;background-color:#f7f7f7;padding:0.4em;}
1568 1570 .shortcut_key{display:inline-block;width:15ex;text-align:right;font-family:monospace;}
1569 1571 .shortcut_descr{display:inline-block;}
1570 div.quickhelp{float:left;width:50%;}
1571 1572 span#save_widget{padding:0px 5px;margin-top:12px;}
1572 1573 span#checkpoint_status,span#autosave_status{font-size:small;}
1573 1574 @media (max-width:767px){span#save_widget{font-size:small;} span#checkpoint_status,span#autosave_status{font-size:x-small;}}@media (max-width:767px){span#checkpoint_status,span#autosave_status{display:none;}}@media (min-width:768px) and (max-width:979px){span#checkpoint_status{display:none;} span#autosave_status{font-size:x-small;}}.toolbar{padding:0px 10px;margin-top:-5px;}.toolbar select,.toolbar label{width:auto;height:26px;vertical-align:middle;margin-right:2px;margin-bottom:0px;display:inline;font-size:92%;margin-left:0.3em;margin-right:0.3em;padding:0px;padding-top:3px;}
@@ -140,8 +140,10 b' class="notebook_app"'
140 140 <ul class="dropdown-menu">
141 141 <li id="run_cell" title="Run this cell, and move cursor to the next one">
142 142 <a href="#">Run</a></li>
143 <li id="run_cell_in_place" title="Run this cell, without moving to the next one">
144 <a href="#">Run in Place</a></li>
143 <li id="run_cell_select_below" title="Run this cell, select below">
144 <a href="#">Run and Select Below</a></li>
145 <li id="run_cell_insert_below" title="Run this cell, insert below">
146 <a href="#">Run and Insert Below</a></li>
145 147 <li id="run_all_cells" title="Run all cells in the notebook">
146 148 <a href="#">Run All</a></li>
147 149 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
@@ -291,6 +293,7 b' class="notebook_app"'
291 293 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
292 294 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
293 295 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
296 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
294 297 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
295 298 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
296 299 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
@@ -31,11 +31,13 b' casper.notebook_test(function () {'
31 31 var result = this.get_output_cell(0);
32 32 var num_cells = this.get_cells_length();
33 33 this.test.assertEquals(result.text, '11\n', 'cell execute (using ctrl-enter)');
34 this.test.assertEquals(num_cells, 1, ' ^--- does not add a new cell')
34 this.test.assertEquals(num_cells, 2, 'ctrl-enter adds a new cell at the bottom')
35 35 });
36 36
37 37 // do it again with the keyboard shortcut
38 38 this.thenEvaluate(function () {
39 IPython.notebook.select(1);
40 IPython.notebook.delete_cell();
39 41 var cell = IPython.notebook.get_cell(0);
40 42 cell.set_text('a=12; print(a)');
41 43 cell.clear_output();
@@ -48,7 +50,7 b' casper.notebook_test(function () {'
48 50 var result = this.get_output_cell(0);
49 51 var num_cells = this.get_cells_length();
50 52 this.test.assertEquals(result.text, '12\n', 'cell execute (using shift-enter)');
51 this.test.assertEquals(num_cells, 2, ' ^--- adds a new cell')
53 this.test.assertEquals(num_cells, 1, 'shift-enter adds no new cell at the bottom')
52 54 });
53 55
54 56 // press the "play" triangle button in the toolbar
@@ -4,11 +4,12 b''
4 4 casper.notebook_test(function() {
5 5 var output = this.evaluate(function () {
6 6 // Fill in test data.
7 IPython.notebook.command_mode();
7 8 var set_cell_text = function () {
8 9 var cell_one = IPython.notebook.get_selected_cell();
9 10 cell_one.set_text('a = 5');
10 11
11 IPython.notebook.insert_cell_below('code');
12 IPython.utils.press(IPython.keycodes.b)
12 13 var cell_two = IPython.notebook.get_selected_cell();
13 14 cell_two.set_text('print(a)');
14 15 };
@@ -31,7 +32,7 b' casper.notebook_test(function() {'
31 32 });
32 33
33 34 this.test.assertEquals(output.above, 'a = 5\nprint(a)',
34 'Successful insert_cell_above().');
35 'Successful merge_cell_above().');
35 36 this.test.assertEquals(output.below, 'a = 5\nprint(a)',
36 'Successful insert_cell_below().');
37 'Successful merge_cell_below().');
37 38 });
@@ -3,10 +3,11 b''
3 3 //
4 4 casper.notebook_test(function () {
5 5 var result = this.evaluate(function() {
6 IPython.notebook.command_mode();
6 7 pos0 = IPython.notebook.get_selected_index();
7 IPython.notebook.insert_cell_below('code');
8 IPython.utils.press(IPython.keycodes.b)
8 9 pos1 = IPython.notebook.get_selected_index();
9 IPython.notebook.insert_cell_below('code');
10 IPython.utils.press(IPython.keycodes.b)
10 11 pos2 = IPython.notebook.get_selected_index();
11 12 // Simulate the "up arrow" and "down arrow" keys.
12 13 IPython.utils.press_up();
General Comments 0
You need to be logged in to leave comments. Login now